当我在 C 中调用 ASM 函数时,参数不会出现在堆栈中
When I call A ASM function in C the parameters don't appear on the stack
好的,我有两个文件,一个是 C 文件,另一个是 ASM 文件。他们的代码是。
C档
void print()
{
print_char('A');
}
ASM 文件
print_char:
push ebp ; prolouge
mov ebp, esp
mov ah, 0eh ; set code for printing
mov al, [esp+8] ; move char into al
int 10h ; call int 10h
mov esp, ebp ; epilouge
pop ebp
ret
它会打印一个叫做 A 三重条的东西。 Doe 有谁知道为什么吗
我有一个 Whosebug 答案,其中有很多 including setting up segments, the stack, and not relying on registers to have particular values etc. I have also have a Whosebug answer that is a 可以从 1.44MB 的软盘映像加载内核(或第二阶段)。
如果Int 10h/ah=0eh
is printing characters and not faulting then the code you are trying to generate appears to be intended to run in real mode. You mention you are using Ubuntu don't mention if you are using a cross compiler (I highly recommend using one). The other possibility is that you are using the experimental ia16-elf-gcc编译器可以生成16位实模式代码,可以运行在从8086到80386+的各种处理器上。
在没有任何其他信息的情况下,我假设您在 Ubuntu 上使用的是本机 gcc
编译器,而不是交叉编译器或 ia16-gcc 编译器。您应该知道,本地 GCC 可以使用 -m16
选项在实模式下生成 运行 的代码,但生成的代码只会在 80386+ 或更好的处理器上 运行。除非你竭尽全力,否则 GCC 生成的代码应该放在内存的前 64KiB 中,我建议使用 CS=DS=ES=SS=0 以尽量减少麻烦。主流 GCC 不了解实模式 segment:offset 寻址。
您没有真正展示足够多的代码来了解您遇到的所有问题。对我来说最突出的是 print_char
:
print_char:
push ebp ; prolouge
mov ebp, esp
mov ah, 0eh ; set code for printing
mov al, [esp+8] ; move char into al
int 10h ; call int 10h
mov esp, ebp ; epilouge
pop ebp
ret
特别是 ret
会引起问题。因为 GCC 正在生成具有 32 位操作数和 32 位地址覆盖的代码,所以它期望调用函数将 32 位 return 地址压入堆栈。在实模式下,汇编代码中的一个简单 ret
将 return 到堆栈上的 16 位地址,并且只弹出 2 个字节而不是 4 个字节。这将导致堆栈不平衡 returns 给调用者,这可能会导致不可预知的结果。要解决此问题,您需要在 ret
上使用 32 位操作数覆盖,因此它必须是 o32 ret
.
如果您从程序集调用 C 函数,您必须确保您使用的 CALL
版本将完整的 32 位地址推送到堆栈而不是 16 位堆栈。如果不这样做,将导致堆栈上传递的任何参数被错误地索引,并且 GCC 将执行 ret
,期望从堆栈中弹出 32 位 return 地址。如果您有一个名为 kmain
的函数(以内核入口点为例),那么您可能会考虑以这种方式对 CALL
进行编码:
call kmain
你真正需要的是:
call dword kmain
您可能需要查看的其他问题:
- 确保在调用 C 代码之前正确设置了段寄存器
- 设置合适的堆栈指针
SS:SP
- 将 DF 标志设置为 0(使用
CLD
)
- 如果使用浮点 x87 浮点使用
finit
初始化它
- 在从程序集调用任何 C 之前,请确保堆栈在 16 字节边界上对齐。
- 确保您的整个内核已加载到内存中
- 您需要在 C 代码使用任何未初始化或零初始化的全局变量之前将 BSS 部分清零
- 确保生成的代码具有适当的 VMA/Origin 点。
- 确保 C 代码不是使用位置独立代码 (PIC) 使用
-fno-pic
构建的
- 确保 C 代码是使用
-m16
选项构建的,这样生成的指令将在 运行 在 16 位实模式下运行一个 80386+.
- 使用
-fno-asynchronous-unwind-tables
删除异步展开表并使用 -fno-exceptions
关闭异常
作为示例,我将使用我的引导加载程序在 0x7e00 加载内核。调用 print_char
和一个简单的 print_string
.
boot.asm:
STAGE2_ABS_ADDR equ 0x07e00
STAGE2_RUN_SEG equ 0x0000
STAGE2_RUN_OFS equ STAGE2_ABS_ADDR
; Run stage2 with segment of 0x0000 and offset of 0x7e00
STAGE2_LOAD_SEG equ STAGE2_ABS_ADDR>>4
; Segment to start reading Stage2 into
; right after bootloader
STAGE2_LBA_START equ 1 ; Logical Block Address(LBA) Stage2 starts on
; LBA 1 = sector after boot sector
STAGE2_LBA_END equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES equ 3 ; Number of times to retry on disk error
bits 16
ORG 0x7c00
; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
%ifdef WITH_BPB
%include "bpb.inc"
%endif
boot_start:
xor ax, ax ; DS=SS=0 for stage2 loading
mov ds, ax
mov ss, ax ; Stack at 0x0000:0x7c00
mov sp, 0x7c00
cld ; Set string instructions to use forward movement
; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
mov [bootDevice], dl ; Save boot drive
mov di, STAGE2_LOAD_SEG ; DI = Current segment to read into
mov si, STAGE2_LBA_START ; SI = LBA that stage2 starts at
jmp .chk_for_last_lba ; Check to see if we are last sector in stage2
.read_sector_loop:
mov bp, DISK_RETRIES ; Set disk retry count
call lba_to_chs ; Convert current LBA to CHS
mov es, di ; Set ES to current segment number to read into
xor bx, bx ; Offset zero in segment
.retry:
mov ax, 0x0201 ; Call function 0x02 of int 13h (read sectors)
; AL = 1 = Sectors to read
int 0x13 ; BIOS Disk interrupt call
jc .disk_error ; If CF set then disk error
.success:
add di, 512>>4 ; Advance to next 512 byte segment (0x20*16=512)
inc si ; Next LBA
.chk_for_last_lba:
cmp si, STAGE2_LBA_END ; Have we reached the last stage2 sector?
jl .read_sector_loop ; If we haven't then read next sector
.stage2_loaded:
mov ax, STAGE2_RUN_SEG ; Set up the segments appropriate for Stage2 to run
mov ds, ax
mov es, ax
; FAR JMP to the Stage2 entry point at physical address 0x07e00
xor ax, ax ; ES=FS=GS=0 (DS zeroed earlier)
mov es, ax
mov fs, ax
mov gs, ax
; SS:SP is already at 0x0000:0x7c00, keep it that way
; DL still contains the boot drive number
; Far jump to second stage at 0x0000:0x7e00
jmp STAGE2_RUN_SEG:STAGE2_RUN_OFS
.disk_error:
xor ah, ah ; Int13h/AH=0 is drive reset
int 0x13
dec bp ; Decrease retry count
jge .retry ; If retry count not exceeded then try again
error_end:
; Unrecoverable error; print drive error; enter infinite loop
mov si, diskErrorMsg ; Display disk error message
call print_string
cli
.error_loop:
hlt
jmp .error_loop
; Function: print_string
; Display a string to the console on display page 0
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
print_string:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
; Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;
; Resources: http://www.ctyme.com/intr/rb-0607.htm
; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;
; Sector = (LBA mod SPT) + 1
; Head = (LBA / SPT) mod HEADS
; Cylinder = (LBA / SPT) / HEADS
;
; Inputs: SI = LBA
; Outputs: DL = Boot Drive Number
; DH = Head
; CH = Cylinder (lower 8 bits of 10-bit cylinder)
; CL = Sector/Cylinder
; Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
; Sector in lower 6 bits of CL
;
; Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
push ax ; Preserve AX
mov ax, si ; Copy LBA to AX
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [numHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [bootDevice] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
shl ah, 6 ; Store upper 2 bits of 10-bit Cylinder into
or cl, ah ; upper 2 bits of Sector (CL)
pop ax ; Restore scratch registers
ret
; If not using a BPB (via bpb.inc) provide default Heads and SPT values
%ifndef WITH_BPB
numHeads: dw 2 ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif
bootDevice: db 0x00
diskErrorMsg: db "Unrecoverable disk error!", 0
; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db 0
dw 0xaa55
; Beginning of stage2. This is at 0x7E00 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.
NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
; Number of 512 byte sectors stage2 uses.
stage2_start:
; Insert stage2 binary here. It is done this way since we
; can determine the size(and number of sectors) to load since
; Size = stage2_end-stage2_start
incbin "stage2.bin"
; End of stage2. Make sure this label is LAST in this file!
stage2_end:
; Fill out this file to produce a 1.44MB floppy image
TIMES 1024*1440-($-$$) db 0x00
kernel.c:
extern void print_char(const char inchar);
void print_string(const char *string)
{
while (*string)
print_char(*string++);
}
void kmain(unsigned int drive_num)
{
(void) drive_num; /* Quiet compiler warning / unused variable */
print_char('A'); /* Print A */
print_char(13); /* Print Carriage Return */
print_char(10); /* Print Line Feed */
print_string("Hello, world!\r\nThis is a test!\r\n");
return;
}
tty.asm:
bits 16
global print_char
print_char:
; Removed the prologue and epilogue code as it isn't needed
push bx ; BX is non volatile register we need to save it
mov ah, 0eh ; set code for printing
mov al, [esp+6] ; move char into al
xor bx, bx ; Ensure page 0 (BH = 0), BL is color if in graphics mode
int 10h ; call int 10h
pop bx
o32 ret ; We need to do a long return because the return address
; the C code put on the stack was a 4 byte return address.
; Failure to get this right can corrupt the stack
entry.asm:
bits 16
extern kmain
extern __bss_start
extern __bss_sizel
global _start
; The linker script will place .text.entry before other sections.
section .text.entry
_start:
; DL - drive number we booted as
xor ax, ax ; DS=ES=SS=0 (CS was already set to 0)
mov es, ax
mov ds, ax
mov ss, ax
mov esp, 0x7c00-16 ; SS:SP is 0x0000:0x7c00 below the bootloader
; Create stack space to pass drive number as parameter and
; ensure ESP is still 16 byte aligned before calling kmain
finit ; Initialize x87 FPU
cld ; Set Direction Flag (DF) is cleared (forward movement)
sti ; Enable interrupts
; Zero out the BSS memory area a DWORD at a time
; since the memory isn't guaranteed to already be zero
xor eax, eax
mov ecx, __bss_sizel
mov edi, __bss_start
rep stosd
movzx edx, dl ; Zero extend drive number to 32-bit value
mov [esp], edx ; Pass drive number as first parameter to kmain
call dword kmain ; We need to call C functions with `dword` so a 32-bit
; return address is on the stack
.hltloop: ; Infinite loop to end the kernel
hlt
jmp .hltloop
link.ld:
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 0x7e00;
.text : SUBALIGN(4)
{
*(.text.entry) /* Ensure .text.entry appears first */
*(.text*)
*(.rodata*)
*(.data)
}
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON) /* all COMMON sections from all files */
*(.bss) /* all BSS sections from all files */
}
. = ALIGN(4);
__bss_end = .;
__bss_sizeb = __bss_end - __bss_start; /* BSS size in bytes */
__bss_sizel = (__bss_end - __bss_start) / 4; /* BSS size in longs/DWORDs */
/DISCARD/ : { /* Remove Unneeded sections */
*(.eh_frame);
*(.comment);
}
__end = .;
}
为了 compile/assemble/link 你可以使用这些命令:
# Build kernel assembly files
nasm -f elf32 entry.asm -o entry.o
nasm -f elf32 tty.asm -o tty.o
# Compile the C files
gcc -c -Wall -Wextra -m16 -O3 -ffreestanding -fno-exceptions \
-fno-asynchronous-unwind-tables -fno-pic kernel.c -o kernel.o
# Link the files to an 32-bit ELF executable using a linker script
ld -Tlink.ld -melf_i386 -nostartfiles -nostdlib \
tty.o entry.o kernel.o -o kernel.elf
# Convert the ELF executable to a binary file that can be loaded by the bootloader
objcopy -O binary kernel.elf stage2.bin
# Generate the bootloader/disk image
nasm -f bin boot.asm -o disk.img
您可以 运行 使用 QEMU 使用它:
qemu-system-i386 -fda disk.img
您可以 运行 使用 BOCHS 使用:
bochs -qf /dev/null 'boot:floppy' \
'floppya: type=1_44, 1_44="disk.img", status=inserted, write_protected=0'
我推荐 BOCHS 来调试实模式代码。当 运行 它应该输出类似于:
好的,我有两个文件,一个是 C 文件,另一个是 ASM 文件。他们的代码是。
C档
void print()
{
print_char('A');
}
ASM 文件
print_char:
push ebp ; prolouge
mov ebp, esp
mov ah, 0eh ; set code for printing
mov al, [esp+8] ; move char into al
int 10h ; call int 10h
mov esp, ebp ; epilouge
pop ebp
ret
它会打印一个叫做 A 三重条的东西。 Doe 有谁知道为什么吗
我有一个 Whosebug 答案,其中有很多
如果Int 10h/ah=0eh
is printing characters and not faulting then the code you are trying to generate appears to be intended to run in real mode. You mention you are using Ubuntu don't mention if you are using a cross compiler (I highly recommend using one). The other possibility is that you are using the experimental ia16-elf-gcc编译器可以生成16位实模式代码,可以运行在从8086到80386+的各种处理器上。
在没有任何其他信息的情况下,我假设您在 Ubuntu 上使用的是本机 gcc
编译器,而不是交叉编译器或 ia16-gcc 编译器。您应该知道,本地 GCC 可以使用 -m16
选项在实模式下生成 运行 的代码,但生成的代码只会在 80386+ 或更好的处理器上 运行。除非你竭尽全力,否则 GCC 生成的代码应该放在内存的前 64KiB 中,我建议使用 CS=DS=ES=SS=0 以尽量减少麻烦。主流 GCC 不了解实模式 segment:offset 寻址。
您没有真正展示足够多的代码来了解您遇到的所有问题。对我来说最突出的是 print_char
:
print_char:
push ebp ; prolouge
mov ebp, esp
mov ah, 0eh ; set code for printing
mov al, [esp+8] ; move char into al
int 10h ; call int 10h
mov esp, ebp ; epilouge
pop ebp
ret
特别是 ret
会引起问题。因为 GCC 正在生成具有 32 位操作数和 32 位地址覆盖的代码,所以它期望调用函数将 32 位 return 地址压入堆栈。在实模式下,汇编代码中的一个简单 ret
将 return 到堆栈上的 16 位地址,并且只弹出 2 个字节而不是 4 个字节。这将导致堆栈不平衡 returns 给调用者,这可能会导致不可预知的结果。要解决此问题,您需要在 ret
上使用 32 位操作数覆盖,因此它必须是 o32 ret
.
如果您从程序集调用 C 函数,您必须确保您使用的 CALL
版本将完整的 32 位地址推送到堆栈而不是 16 位堆栈。如果不这样做,将导致堆栈上传递的任何参数被错误地索引,并且 GCC 将执行 ret
,期望从堆栈中弹出 32 位 return 地址。如果您有一个名为 kmain
的函数(以内核入口点为例),那么您可能会考虑以这种方式对 CALL
进行编码:
call kmain
你真正需要的是:
call dword kmain
您可能需要查看的其他问题:
- 确保在调用 C 代码之前正确设置了段寄存器
- 设置合适的堆栈指针
SS:SP
- 将 DF 标志设置为 0(使用
CLD
) - 如果使用浮点 x87 浮点使用
finit
初始化它 - 在从程序集调用任何 C 之前,请确保堆栈在 16 字节边界上对齐。
- 确保您的整个内核已加载到内存中
- 您需要在 C 代码使用任何未初始化或零初始化的全局变量之前将 BSS 部分清零
- 确保生成的代码具有适当的 VMA/Origin 点。
- 确保 C 代码不是使用位置独立代码 (PIC) 使用
-fno-pic
构建的
- 确保 C 代码是使用
-m16
选项构建的,这样生成的指令将在 运行 在 16 位实模式下运行一个 80386+. - 使用
-fno-asynchronous-unwind-tables
删除异步展开表并使用-fno-exceptions
关闭异常
作为示例,我将使用我的引导加载程序在 0x7e00 加载内核。调用 print_char
和一个简单的 print_string
.
boot.asm:
STAGE2_ABS_ADDR equ 0x07e00
STAGE2_RUN_SEG equ 0x0000
STAGE2_RUN_OFS equ STAGE2_ABS_ADDR
; Run stage2 with segment of 0x0000 and offset of 0x7e00
STAGE2_LOAD_SEG equ STAGE2_ABS_ADDR>>4
; Segment to start reading Stage2 into
; right after bootloader
STAGE2_LBA_START equ 1 ; Logical Block Address(LBA) Stage2 starts on
; LBA 1 = sector after boot sector
STAGE2_LBA_END equ STAGE2_LBA_START + NUM_STAGE2_SECTORS
; Logical Block Address(LBA) Stage2 ends at
DISK_RETRIES equ 3 ; Number of times to retry on disk error
bits 16
ORG 0x7c00
; Include a BPB (1.44MB floppy with FAT12) to be more compatible with USB floppy media
%ifdef WITH_BPB
%include "bpb.inc"
%endif
boot_start:
xor ax, ax ; DS=SS=0 for stage2 loading
mov ds, ax
mov ss, ax ; Stack at 0x0000:0x7c00
mov sp, 0x7c00
cld ; Set string instructions to use forward movement
; Read Stage2 1 sector at a time until stage2 is completely loaded
load_stage2:
mov [bootDevice], dl ; Save boot drive
mov di, STAGE2_LOAD_SEG ; DI = Current segment to read into
mov si, STAGE2_LBA_START ; SI = LBA that stage2 starts at
jmp .chk_for_last_lba ; Check to see if we are last sector in stage2
.read_sector_loop:
mov bp, DISK_RETRIES ; Set disk retry count
call lba_to_chs ; Convert current LBA to CHS
mov es, di ; Set ES to current segment number to read into
xor bx, bx ; Offset zero in segment
.retry:
mov ax, 0x0201 ; Call function 0x02 of int 13h (read sectors)
; AL = 1 = Sectors to read
int 0x13 ; BIOS Disk interrupt call
jc .disk_error ; If CF set then disk error
.success:
add di, 512>>4 ; Advance to next 512 byte segment (0x20*16=512)
inc si ; Next LBA
.chk_for_last_lba:
cmp si, STAGE2_LBA_END ; Have we reached the last stage2 sector?
jl .read_sector_loop ; If we haven't then read next sector
.stage2_loaded:
mov ax, STAGE2_RUN_SEG ; Set up the segments appropriate for Stage2 to run
mov ds, ax
mov es, ax
; FAR JMP to the Stage2 entry point at physical address 0x07e00
xor ax, ax ; ES=FS=GS=0 (DS zeroed earlier)
mov es, ax
mov fs, ax
mov gs, ax
; SS:SP is already at 0x0000:0x7c00, keep it that way
; DL still contains the boot drive number
; Far jump to second stage at 0x0000:0x7e00
jmp STAGE2_RUN_SEG:STAGE2_RUN_OFS
.disk_error:
xor ah, ah ; Int13h/AH=0 is drive reset
int 0x13
dec bp ; Decrease retry count
jge .retry ; If retry count not exceeded then try again
error_end:
; Unrecoverable error; print drive error; enter infinite loop
mov si, diskErrorMsg ; Display disk error message
call print_string
cli
.error_loop:
hlt
jmp .error_loop
; Function: print_string
; Display a string to the console on display page 0
;
; Inputs: SI = Offset of address to print
; Clobbers: AX, BX, SI
print_string:
mov ah, 0x0e ; BIOS tty Print
xor bx, bx ; Set display page to 0 (BL)
jmp .getch
.repeat:
int 0x10 ; print character
.getch:
lodsb ; Get character from string
test al,al ; Have we reached end of string?
jnz .repeat ; if not process next character
.end:
ret
; Function: lba_to_chs
; Description: Translate Logical block address to CHS (Cylinder, Head, Sector).
;
; Resources: http://www.ctyme.com/intr/rb-0607.htm
; https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
;
; Sector = (LBA mod SPT) + 1
; Head = (LBA / SPT) mod HEADS
; Cylinder = (LBA / SPT) / HEADS
;
; Inputs: SI = LBA
; Outputs: DL = Boot Drive Number
; DH = Head
; CH = Cylinder (lower 8 bits of 10-bit cylinder)
; CL = Sector/Cylinder
; Upper 2 bits of 10-bit Cylinders in upper 2 bits of CL
; Sector in lower 6 bits of CL
;
; Notes: Output registers match expectation of Int 13h/AH=2 inputs
;
lba_to_chs:
push ax ; Preserve AX
mov ax, si ; Copy LBA to AX
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [sectorsPerTrack] ; 32-bit by 16-bit DIV : LBA / SPT
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [numHeads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [bootDevice] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
shl ah, 6 ; Store upper 2 bits of 10-bit Cylinder into
or cl, ah ; upper 2 bits of Sector (CL)
pop ax ; Restore scratch registers
ret
; If not using a BPB (via bpb.inc) provide default Heads and SPT values
%ifndef WITH_BPB
numHeads: dw 2 ; 1.44MB Floppy has 2 heads & 18 sector per track
sectorsPerTrack: dw 18
%endif
bootDevice: db 0x00
diskErrorMsg: db "Unrecoverable disk error!", 0
; Pad boot sector to 510 bytes and add 2 byte boot signature for 512 total bytes
TIMES 510-($-$$) db 0
dw 0xaa55
; Beginning of stage2. This is at 0x7E00 and will allow your stage2 to be 32.5KiB
; before running into problems. DL will be set to the drive number originally
; passed to us by the BIOS.
NUM_STAGE2_SECTORS equ (stage2_end-stage2_start+511) / 512
; Number of 512 byte sectors stage2 uses.
stage2_start:
; Insert stage2 binary here. It is done this way since we
; can determine the size(and number of sectors) to load since
; Size = stage2_end-stage2_start
incbin "stage2.bin"
; End of stage2. Make sure this label is LAST in this file!
stage2_end:
; Fill out this file to produce a 1.44MB floppy image
TIMES 1024*1440-($-$$) db 0x00
kernel.c:
extern void print_char(const char inchar);
void print_string(const char *string)
{
while (*string)
print_char(*string++);
}
void kmain(unsigned int drive_num)
{
(void) drive_num; /* Quiet compiler warning / unused variable */
print_char('A'); /* Print A */
print_char(13); /* Print Carriage Return */
print_char(10); /* Print Line Feed */
print_string("Hello, world!\r\nThis is a test!\r\n");
return;
}
tty.asm:
bits 16
global print_char
print_char:
; Removed the prologue and epilogue code as it isn't needed
push bx ; BX is non volatile register we need to save it
mov ah, 0eh ; set code for printing
mov al, [esp+6] ; move char into al
xor bx, bx ; Ensure page 0 (BH = 0), BL is color if in graphics mode
int 10h ; call int 10h
pop bx
o32 ret ; We need to do a long return because the return address
; the C code put on the stack was a 4 byte return address.
; Failure to get this right can corrupt the stack
entry.asm:
bits 16
extern kmain
extern __bss_start
extern __bss_sizel
global _start
; The linker script will place .text.entry before other sections.
section .text.entry
_start:
; DL - drive number we booted as
xor ax, ax ; DS=ES=SS=0 (CS was already set to 0)
mov es, ax
mov ds, ax
mov ss, ax
mov esp, 0x7c00-16 ; SS:SP is 0x0000:0x7c00 below the bootloader
; Create stack space to pass drive number as parameter and
; ensure ESP is still 16 byte aligned before calling kmain
finit ; Initialize x87 FPU
cld ; Set Direction Flag (DF) is cleared (forward movement)
sti ; Enable interrupts
; Zero out the BSS memory area a DWORD at a time
; since the memory isn't guaranteed to already be zero
xor eax, eax
mov ecx, __bss_sizel
mov edi, __bss_start
rep stosd
movzx edx, dl ; Zero extend drive number to 32-bit value
mov [esp], edx ; Pass drive number as first parameter to kmain
call dword kmain ; We need to call C functions with `dword` so a 32-bit
; return address is on the stack
.hltloop: ; Infinite loop to end the kernel
hlt
jmp .hltloop
link.ld:
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 0x7e00;
.text : SUBALIGN(4)
{
*(.text.entry) /* Ensure .text.entry appears first */
*(.text*)
*(.rodata*)
*(.data)
}
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON) /* all COMMON sections from all files */
*(.bss) /* all BSS sections from all files */
}
. = ALIGN(4);
__bss_end = .;
__bss_sizeb = __bss_end - __bss_start; /* BSS size in bytes */
__bss_sizel = (__bss_end - __bss_start) / 4; /* BSS size in longs/DWORDs */
/DISCARD/ : { /* Remove Unneeded sections */
*(.eh_frame);
*(.comment);
}
__end = .;
}
为了 compile/assemble/link 你可以使用这些命令:
# Build kernel assembly files
nasm -f elf32 entry.asm -o entry.o
nasm -f elf32 tty.asm -o tty.o
# Compile the C files
gcc -c -Wall -Wextra -m16 -O3 -ffreestanding -fno-exceptions \
-fno-asynchronous-unwind-tables -fno-pic kernel.c -o kernel.o
# Link the files to an 32-bit ELF executable using a linker script
ld -Tlink.ld -melf_i386 -nostartfiles -nostdlib \
tty.o entry.o kernel.o -o kernel.elf
# Convert the ELF executable to a binary file that can be loaded by the bootloader
objcopy -O binary kernel.elf stage2.bin
# Generate the bootloader/disk image
nasm -f bin boot.asm -o disk.img
您可以 运行 使用 QEMU 使用它:
qemu-system-i386 -fda disk.img
您可以 运行 使用 BOCHS 使用:
bochs -qf /dev/null 'boot:floppy' \
'floppya: type=1_44, 1_44="disk.img", status=inserted, write_protected=0'
我推荐 BOCHS 来调试实模式代码。当 运行 它应该输出类似于: