Mar*_*yse 5 x86 gcc nasm inline-assembly bootloader
我在asm中编写了一个引导加载程序,并希望在我的项目中添加一些已编译的C代码.
我在这里创建了一个测试函数:
test.c的
__asm__(".code16\n");
void print_str() {
__asm__ __volatile__("mov $'A' , %al\n");
__asm__ __volatile__("mov $0x0e, %ah\n");
__asm__ __volatile__("int $0x10\n");
}
Run Code Online (Sandbox Code Playgroud)
这是asm代码(引导加载程序):
hw.asm
[org 0x7C00]
[BITS 16]
[extern print_str] ;nasm tip
start:
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
mov si, name
call print_string
mov al, ' '
int 10h
mov si, version
call print_string
mov si, line_return
call print_string
call print_str ;call function
mov si, welcome
call print_string
jmp mainloop
mainloop:
mov si, prompt
call print_string
mov di, buffer
call get_str
mov si, buffer
cmp byte [si], 0
je mainloop
mov si, buffer
;call print_string
mov di, cmd_version
call strcmp
jc .version
jmp mainloop
.version:
mov si, name
call print_string
mov al, ' '
int 10h
mov si, version
call print_string
mov si, line_return
call print_string
jmp mainloop
name db 'MOS', 0
version db 'v0.1', 0
welcome db 'Developped by Marius Van Nieuwenhuyse', 0x0D, 0x0A, 0
prompt db '>', 0
line_return db 0x0D, 0x0A, 0
buffer times 64 db 0
cmd_version db 'version', 0
%include "functions/print.asm"
%include "functions/getstr.asm"
%include "functions/strcmp.asm"
times 510 - ($-$$) db 0
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)
我需要调用c函数就像一个简单的asm函数没有extern和调用print_str,在VMWare中启动asm脚本.
我试着编译:
nasm -f elf32
Run Code Online (Sandbox Code Playgroud)
但我不能打电话给组织0x7C00
Mic*_*tch 11
虽然有可能,但这个问题的答案比人们想象的要复杂得多.引导加载程序的第一阶段(在物理地址0x07c00处加载的原始512字节)是否可以调用C函数?是的,但需要重新思考如何构建项目.
为了实现这一点,你不能再-f bin使用NASM了.这也意味着你不能使用它org 0x7c00告诉汇编器代码期望从哪个地址开始.您需要通过链接器(直接使用我们的LD或使用GCC进行链接)来完成此操作.由于链接器会将内容放在内存中,因此我们不能依赖将引导扇区签名0xaa55放在输出文件中.我们可以让链接器为我们这样做.
您将发现的第一个问题是GCC内部使用的默认链接器脚本不会按照我们想要的方式进行布局.我们需要创建自己的.这样的链接描述文件必须将原点(虚拟内存地址又名VMA)设置为0x7c00,将数据之前的汇编文件中的代码放在文件中,并将引导签名放在偏移量510处.我不会写关于链接器脚本的教程.该Binutils的文档包含了几乎所有你需要知道的连接器脚本的一切.
OUTPUT_FORMAT("elf32-i386");
/* We define an entry point to keep the linker quiet. This entry point
* has no meaning with a bootloader in the binary image we will eventually
* generate. Bootloader will start executing at whatever is at 0x07c00 */
ENTRY(start);
SECTIONS
{
. = 0x7C00;
.text : {
/* Place the code in hw.o before all other code */
hw.o(.text);
*(.text);
}
/* Place the data after the code */
.data : SUBALIGN(4) {
*(.data);
*(.rodata);
}
/* Place the boot signature at VMA 0x7DFE */
.sig : AT(0x7DFE) {
SHORT(0xaa55);
}
/* Place the uninitialised data in the area after our bootloader
* The BIOS only reads the 512 bytes before this into memory */
. = 0x7E00;
.bss : SUBALIGN(4) {
__bss_start = .;
*(COMMON);
*(.bss)
. = ALIGN(4);
__bss_end = .;
}
__bss_sizeb = SIZEOF(.bss);
/* Remove sections that won't be relevant to us */
/DISCARD/ : {
*(.eh_frame);
*(.comment);
*(.note.gnu.build-id);
}
}
Run Code Online (Sandbox Code Playgroud)
此脚本应创建一个ELF可执行文件,可以使用OBJCOPY将其转换为平面二进制文件.我们可以直接输出二进制文件,但是如果我想在ELF版本中包含调试信息以用于调试目的,我将两个进程分开.
现在我们有了一个链接器脚本,我们必须删除它ORG 0x7c00和引导签名.为简单起见,我们将尝试使用以下代码(hw.asm):
extern print_str
global start
bits 16
section .text
start:
xor ax, ax ; AX = 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
call print_str ; call function
/* Halt the processor so we don't keep executing code beyond this point */
cli
hlt
Run Code Online (Sandbox Code Playgroud)
您可以包含所有其他代码,但此示例仍将演示调用C函数的基础知识.
假设上面的代码现在可以生成ELF从对象hw.asm产生hw.o使用该命令:
nasm -f elf32 hw.asm -o hw.o
Run Code Online (Sandbox Code Playgroud)
您使用以下内容编译每个C文件:
gcc -ffreestanding -c kmain.c -o kmain.o
Run Code Online (Sandbox Code Playgroud)
我将你的C代码放入一个名为的文件中kmain.c.上面的命令将生成kmain.o.我注意到你没有使用交叉编译器,因此你需要使用它-fno-PIE来确保我们不生成可重定位代码.-ffreestanding告诉GCC C标准库可能不存在,main可能不是程序入口点.您将以相同的方式编译每个C文件.
要将此代码链接到最终的可执行文件,然后生成可以启动的平面二进制文件,我们这样做:
ld -melf_i386 -T link.ld kmain.o hw.o -o kernel.elf
objcopy -O binary kernel.elf kernel.bin
Run Code Online (Sandbox Code Playgroud)
您指定要与LD命令链接的所有目标文件.上面的LD命令将生成一个名为32位的ELF可执行文件kernel.elf.此文件将来可用于调试目的.这里我们使用OBJCOPY转换kernel.elf为一个名为的二进制文件kernel.bin.kernel.bin可以用作引导加载程序映像.
您应该能够使用此命令使用QEMU运行它:
qemu-system-i386 -fda kernel.bin
Run Code Online (Sandbox Code Playgroud)
运行时可能看起来像:
你会注意到这封信A出现在最后一行.这是我们对print_str代码的期望.
如果我们在问题中采用您的示例代码:
__asm__ __volatile__("mov $'A' , %al\n");
__asm__ __volatile__("mov $0x0e, %ah\n");
__asm__ __volatile__("int $0x10\n");
Run Code Online (Sandbox Code Playgroud)
如果需要,编译器可以自由地重新排序这些__asm__语句.本int $0x10可以在MOV指令之前出现.如果您希望以这个确切的顺序输出这3行,您可以将它们组合成如下所示:
__asm__ __volatile__("mov $'A' , %al\n\t"
"mov $0x0e, %ah\n\t"
"int $0x10");
Run Code Online (Sandbox Code Playgroud)
这些是基本的汇编语句.它们不需要指定__volatile__它们,因为它们已经隐式挥发,所以它没有效果.从原始海报的答案可以清楚地看出,他们最终希望在__asm__块中使用变量.这对于扩展的内联汇编是可行的(指令字符串后跟冒号:后跟约束.):
使用扩展的asm,您可以从汇编程序读取和写入C变量,并执行从汇编程序代码到C标签的跳转.扩展的asm语法使用冒号(':')来分隔汇编程序模板后的操作数参数:
Run Code Online (Sandbox Code Playgroud)asm [volatile] ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ])
这个答案不是关于内联汇编的教程.一般的经验法则是,除非必须,否则不应使用内联汇编.内联汇编错误可能会导致很难跟踪错误或产生不寻常的副作用.不幸的是,在C中执行16位中断几乎需要它,或者你在汇编中编写整个函数(即:NASM).
这是一个print_chr函数的示例,它接受一个空终止字符串并使用Int 10h/ah = 0ah逐个打印每个字符:
#include <stdint.h>
__asm__(".code16gcc\n");
void print_str(char *str) {
while (*str) {
/* AH=0x0e, AL=char to print, BH=page, BL=fg color */
__asm__ __volatile__ ("int $0x10"
:
: "a" ((0x0e<<8) | *str++),
"b" (0x0000));
}
}
Run Code Online (Sandbox Code Playgroud)
hw.asm 将被修改为如下所示:
push welcome
call print_str ;call function
Run Code Online (Sandbox Code Playgroud)
组装/编译(使用本答案第一部分中的命令)并运行时的想法是打印出welcome消息.不幸的是,它几乎永远不会工作,甚至可能会崩溃像QEMU这样的模拟器.
在上一节中,我们了解到一个带参数的简单函数最终无法正常运行,甚至可能导致像QEMU这样的仿真器崩溃.主要问题是该__asm__(".code16\n");语句对GCC生成的代码效果不佳.该Binutils的AS文件说:
'.code16gcc'为从gcc生成16位代码提供了实验支持,与'.'调用','ret','enter','leave','push','pop',''. pusha','popa','pushf'和'popf'指令默认为32位大小.这使得堆栈指针在函数调用上以相同的方式被操纵,允许在与32位模式相同的堆栈偏移处访问函数参数.'.code16gcc'还会在必要时自动添加地址大小前缀,以使用gcc生成的32位寻址模式.
.code16gcc是你真正需要使用的,而不是.code16.这个强制后端的GNU汇编器在某些指令上发出地址和操作数前缀,这样地址和操作数被视为4字节宽,而不是2字节.
NASM中的手写代码不知道它将调用C指令,NASM也没有像这样的指令.code16gcc.您需要修改汇编代码,以便在实模式下将32位值压入堆栈.您还需要覆盖call指令,以便返回地址需要被视为32位值,而不是16位.这段代码:
push welcome
call print_str ;call function
Run Code Online (Sandbox Code Playgroud)
应该:
jmp 0x0000:setcs
setcs:
cld
push dword welcome
call dword print_str ;call function
Run Code Online (Sandbox Code Playgroud)
GCC要求在调用任何C函数之前清除方向标志.我将CLD指令添加到汇编代码的顶部以确保是这种情况.GCC代码还需要CS到0x0000才能正常工作.该FAR JMP做到了这一点.
您也可以放弃支持该选项的__asm__(".code16gcc\n");现代GCC-m16.-m16自动将a .code16gcc放入正在编译的文件中.
由于GCC还使用完整的32位堆栈指针,因此最好使用0x7c00 初始化ESP,而不仅仅是SP.更改mov sp, 0x7C00到mov esp, 0x7C00.这可确保完整的32位堆栈指针为0x7c00.
修改后的kmain.c代码现在应该如下所示:
#include <stdint.h>
void print_str(char *str) {
while (*str) {
/* AH=0x0e, AL=char to print, BH=page, BL=fg color */
__asm__ __volatile__ ("int $0x10"
:
: "a" ((0x0e<<8) | *str++),
"b" (0x0000));
}
}
Run Code Online (Sandbox Code Playgroud)
并且hw.asm:
extern print_str
global start
bits 16
section .text
start:
xor ax, ax ; AX = 0
mov ds, ax
mov es, ax
mov ss, ax
mov esp, 0x7C00
jmp 0x0000:setcs ; Set CS to 0
setcs:
cld ; GCC code requires direction flag to be cleared
push dword welcome
call dword print_str ; call function
cli
hlt
section .data
welcome db 'Developped by Marius Van Nieuwenhuyse', 0x0D, 0x0A, 0
Run Code Online (Sandbox Code Playgroud)
这些命令可以构建引导加载程序:
gcc -fno-PIC -ffreestanding -m16 -c kmain.c -o kmain.o
ld -melf_i386 -T link.ld kmain.o hw.o -o kernel.elf
objcopy -O binary kernel.elf kernel.bin
Run Code Online (Sandbox Code Playgroud)
与qemu-system-i386 -fda kernel.bin它一起运行应该看起来类似于:
GCC生成的代码有许多缺点.code16gcc:
如果你想从更现代的C编译器生成真正的16位代码,我推荐使用OpenWatcom C.
wlink Watcom链接器可以生成可用作引导加载程序的基本平面二进制文件.BIOS启动顺序不保证内存实际上为零.这导致零初始化区域BSS的潜在问题.在第一次调用C代码之前,区域应该由汇编代码填充零.我最初编写的链接描述文件定义了一个符号__bss_start,它是BSS内存的偏移量,__bss_sizeb是以字节为单位的大小.使用此信息,您可以使用STOSB指令轻松将其填充.在顶部hw.asm你可以添加:
extern __bss_sizeb
extern __bss_start
Run Code Online (Sandbox Code Playgroud)
在CLD指令之后和调用任何C代码之前,你可以这样做零填充:
; Zero fill the BSS section
mov cx, __bss_sizeb ; Size of BSS computed in linker script
mov di, __bss_start ; Start of BSS defined in linker script
rep stosb ; AL still zero, Fill memory with zero
Run Code Online (Sandbox Code Playgroud)
为了减少编译器生成的代码膨胀,可以使用它-fomit-frame-pointer.编译-Os可以优化空间(而不是速度).我们为BIOS加载的初始代码提供了有限的空间(512字节),因此这些优化可能是有益的.用于编译的命令行可能显示为:
gcc -fno-PIC -fomit-frame-pointer -ffreestanding -m16 -Os -c kmain.c -o kmain.o
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1353 次 |
| 最近记录: |