Cygwin 上的 GCC 在 C 代码中使用全局 NASM 符号时编译废话
GCC on Cygwin compiling nonsense when using global NASM symbols in C code
我正在编写一个小型 64 位引导加载程序来探索汇编语言及其与 C 代码的交互。我正在用 NASM 编译汇编部分和 GCC 中的 C 部分,然后用 ld 将所有链接在一起,并用 objcopy 提取纯代码。该代码旨在 运行 没有 Grub 或任何其他引导加载程序:它正在将自身从软盘加载到内存中。目前,我正在研究 C 函数如何使用 NASM 中定义的符号,我正在为一些我认为“简单”的事情而苦苦挣扎:
我在 NASM 中定义了一个全局变量,它被放置在自定义部分中。这样做的原因是我希望这个变量在> 0xffff800000000000(内核space)范围内有一个虚拟地址。我正在处理我的链接描述文件中的寻址,见下文。该变量在汇编文件中定义如下:
section .kdata
global xyz_foo_bar
xyz_foo_bar:
dq 0
在 C 代码中,我声明了一个仅递增该全局变量的函数:
extern unsigned long xyz_foo_bar;
void test_xyz_inc() {
xyz_foo_bar++;
}
这显然已成功编译和链接。
然而,当我看反汇编函数时,我不明白我看到的是什么。
objdump.exe -M intel -d boot1.elf
...
ffff800000008f73 <test_xyz_inc>:
ffff800000008f73: 55 push rbp
ffff800000008f74: 48 89 e5 mov rbp,rsp
ffff800000008f77: 48 8b 05 00 00 00 00 mov rax,QWORD PTR [rip+0x0] # ffff800000008f7e <test_xyz_inc+0xb>
ffff800000008f7e: 48 8b 00 mov rax,QWORD PTR [rax]
ffff800000008f81: 48 8d 50 01 lea rdx,[rax+0x1]
ffff800000008f85: 48 8b 05 00 00 00 00 mov rax,QWORD PTR [rip+0x0] # ffff800000008f8c <test_xyz_inc+0x19>
ffff800000008f8c: 48 89 10 mov QWORD PTR [rax],rdx
ffff800000008f8f: 90 nop
ffff800000008f90: 5d pop rbp
ffff800000008f91: c3 ret
地址 0xffff800000008f77:当我解释它试图取消对 RIP 的引用而没有位移并将生成的 qword 作为 RAX 的输入时,我是对的吗?它有什么意义?我的猜测是编译器/链接器没有正确计算位移。
下面是我编译代码的方式:
nasm -o boot1.o -l boot1.lst -f elf64 boot1.asm
gcc -ffreestanding -static-pie -c -mabi=sysv -Wall -o c_functions.o c_functions.c
ld -melf_x86_64 --build-id=none -static --unresolved-symbols=report-all -T boot1.ld boot1.o c_functions.o -o boot1.elf
objcopy -O binary boot1.elf boot1.bin
为了完整起见,这里是链接描述文件:
OUTPUT_FORMAT("elf64-x86-64");
/* 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(main);
INCLUDE boot1-vars.ldinc;
SECTIONS
{
. = load_offset;
.text : {
/* Place the code in boot1.o before all other code */
boot1.o(.text);
}
_text_end = .;
. += code_virtaddr;
.ktext : AT(_ktext_physStart) {
_ktext_physStart = . - code_virtaddr;
boot1.o(.ktext);
c_*.o(.text);
}
.kdata : {
boot1.o(.kdata);
}
. -= code_virtaddr;
/* Place the data after the code */
.data : AT(_data_physStart) {
_data_physStart = .;
*(.data);
*(.rodata*);
}
/* Place the uninitialised data in the area after our bootloader
* The BIOS only reads the 512 bytes before this into memory */
.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/ : {
c_*.o(.*);
}
_end = .;
}
我缺少什么基本的东西吗?
PE: boot1-vars.ldinc的内容,按要求:
load_offset = 0x7C00;
load_page = load_offset >> 12;
load_page_expand = load_page << 12;
pages_to_load = ((_end - load_page) >> 12) + 1;
sectors_to_load = ((_end - load_offset) >> 9) + 1;
mmap_special_page = load_page - 1;
mmap_special_page_virtaddr = mmap_special_page << 12;
mmap_special_page_pagetable = load_page - 2;
mmap_special_page_pagetable_virtaddr = mmap_special_page_pagetable << 12;
pmmalloc_special_page = load_page - 3;
pmmalloc_special_page_virtaddr = pmmalloc_special_page << 12;
pmmalloc_special_page_pagetable = load_page - 4;
pmmalloc_special_page_pagetable_virtaddr = pmmalloc_special_page_pagetable << 12;
mm_pml4_rm_segment = (load_page + pages_to_load) << 8;
mm_pml4_offset = 0;
mm_pml4_offset_0 = (mm_pml4_rm_segment << 4) + mm_pml4_offset;
mm_pml4_offset_1003 = mm_pml4_offset_0 + 0x1003;
mm_pml4_offset_2003 = mm_pml4_offset_0 + 0x2003;
mm_pml4_offset_3003 = mm_pml4_offset_0 + 0x3003;
mm_pml4_offset_4007 = mm_pml4_offset_0 + 0x4007;
mm_pml4_offset_5007 = mm_pml4_offset_0 + 0x5007;
mm_pml4_offset_6003 = mm_pml4_offset_0 + 0x6003;
/* kernel_stack_size = 0x2000; */
trap_div0_virtual = trap_div0;
trap_div0_virtual_16 = trap_div0_virtual & 0xffff;
trap_div0_virtual_shr16 = (trap_div0_virtual >> 16) & 0xffff;
trap_div0_virtual_shr32 = trap_div0_virtual >> 32;
trap_doubleFault_virtual = trap_doubleFault;
trap_doubleFault_virtual_16 = trap_doubleFault_virtual & 0xffff;
trap_doubleFault_virtual_shr16 = (trap_doubleFault_virtual >> 16) & 0xffff;
trap_doubleFault_virtual_shr32 = trap_doubleFault_virtual >> 32;
trap_invalidTSS_virtual = trap_invalidTSS;
trap_invalidTSS_virtual_16 = trap_invalidTSS_virtual & 0xffff;
trap_invalidTSS_virtual_shr16 = (trap_invalidTSS_virtual >> 16) & 0xffff;
trap_invalidTSS_virtual_shr32 = trap_invalidTSS_virtual >> 32;
trap_generalProtectionFault_virtual = trap_generalProtectionFault;
trap_generalProtectionFault_virtual_16 = trap_generalProtectionFault_virtual & 0xffff;
trap_generalProtectionFault_virtual_shr16 = (trap_generalProtectionFault_virtual >> 16) & 0xffff;
trap_generalProtectionFault_virtual_shr32 = trap_generalProtectionFault_virtual >> 32;
trap_pageFault_virtual = trap_pageFault;
trap_pageFault_virtual_16 = trap_pageFault_virtual & 0xffff;
trap_pageFault_virtual_shr16 = (trap_pageFault_virtual >> 16) & 0xffff;
trap_pageFault_virtual_shr32 = trap_pageFault_virtual >> 32;
trap_invalidSyscall_virtual = trap_invalidSyscall;
trap_invalidSyscall_virtual_16 = trap_invalidSyscall_virtual & 0xffff;
trap_invalidSyscall_virtual_shr16 = (trap_invalidSyscall_virtual >> 16) & 0xffff;
trap_invalidSyscall_virtual_shr32 = trap_invalidSyscall_virtual >> 32;
isr_spurious_virtual = isr_spurious;
isr_spurious_virtual_16 = isr_spurious_virtual & 0xffff;
isr_spurious_virtual_shr16 = (isr_spurious_virtual >> 16) & 0xffff;
isr_spurious_virtual_shr32 = isr_spurious_virtual >> 32;
isr_dummytmr_virtual = isr_dummytmr;
isr_dummytmr_virtual_16 = isr_dummytmr_virtual & 0xffff;
isr_dummytmr_virtual_shr16 = (isr_dummytmr_virtual >> 16) & 0xffff;
isr_dummytmr_virtual_shr32 = isr_dummytmr_virtual >> 32;
isr_userDummy_virtual = isr_userDummy;
isr_userDummy_virtual_16 = isr_userDummy_virtual & 0xffff;
isr_userDummy_virtual_shr16 = (isr_userDummy_virtual >> 16) & 0xffff;
isr_userDummy_virtual_shr32 = isr_userDummy_virtual >> 32;
tss_virtual = code_virtaddr + TSS;
tss_virtual_16 = tss_virtual & 0xffff;
tss_virtual_shr16_8 = (tss_virtual >> 16) & 0xff;
tss_virtual_shr24_8 = (tss_virtual >> 24) & 0xff;
tss_virtual_shr32 = tss_virtual >> 32;
您正在使用 -static-pie
编译 C 代码。生成的代码将需要一个动态加载程序来填充重定位条目。来自 GCC documentation:
-static-pie
Produce a static position independent executable on targets that support it. A static position independent executable is similar to a static executable, but can be loaded at any address without a dynamic linker. For predictable results, you must also specify the same set of options used for compilation (-fpie, -fPIE, or model suboptions) when you specify this linker option.
由于您最终生成的是二进制文件,因此所有重定位信息都消失了。我可以得出结论,您的引导加载程序不能是动态加载程序。它可能只是将二进制文件直接从磁盘读入内存。
如果您使用 objdump -rd
并查看 test_xyz_inc
,您会发现对 xyz_foo_bar
变量的每次访问都有重定位条目。当代码加载到内存中时,这些值通常由动态加载程序 fixed-up。
您真正想要做的是生成 non-PIC 静态代码。编译 C 文件时,将 -static-pie
替换为 -fno-pic
。我还建议在链接时删除 --unresolved-symbols=report-all
因为我相信你通过包含它来掩盖问题。我还相信您应该确保您没有使用 red-zone 编译内核代码,因此我建议您也使用额外的 GCC 选项 -mno-red-zone
。
举个例子:
gcc -ffreestanding -static-pie -c -mabi=sysv -Wall -o c_functions.o c_functions.c
应该是:
gcc -ffreestanding -fno-pic -mno-red-zone -c -mabi=sysv -Wall -o \
c_functions.o c_functions.c
链接时我建议更改:
ld -melf_x86_64 --build-id=none -static \
--unresolved-symbols=report-all -T boot1.ld boot1.o c_functions.o -o boot1.elf
至:
ld -melf_x86_64 --build-id=none -static -T boot1.ld boot1.o c_functions.o -o boot1.elf
Cygwin 观察结果
在 OP 提到他们将 Cygwin 与 GCC 10.2 一起使用后,我碰巧更新了我的 Cygwin 系统,我可以验证即使将 -static-pie
替换为 -fno-pic
生成的代码是静态的并且将 RIP 的所有位移都设置为 0,并且链接器没有说有任何截断。尝试 -mcmodel=large
没有解决问题。我没有时间调查为什么会发生这种情况,但这是使用 x86-64 或 i386/i686 ELF 交叉编译器进行 OS 开发问题较少的一个很好的理由。我建议在 Cygwin 中构建一个 x86-64 ELF 交叉编译器。 OSDev Wiki 上有通用的 guidelines for building a cross compiler。我没有尝试使用 Cygwin 进行这样的构建,所以我不确定是否有任何障碍使它比 Linux.
上的构建更困难
故事结束
在听取了@MichaelPetch 的一些建议后,我在 Cygwin 中为 x86_64-elf 目标构建了一个交叉编译器和 binutils。我关注了这些 OSDev Wiki 页面:
该组合似乎工作正常,因为缺少的 RIP-relative 位移设置正确,并且从汇编代码中调用 C 函数不再像以前那样导致一般保护错误.
注意:为了使 binutils 正常工作,我必须按照此处所述修补源代码,否则 gdb 将不想被链接:
非常感谢!
我正在编写一个小型 64 位引导加载程序来探索汇编语言及其与 C 代码的交互。我正在用 NASM 编译汇编部分和 GCC 中的 C 部分,然后用 ld 将所有链接在一起,并用 objcopy 提取纯代码。该代码旨在 运行 没有 Grub 或任何其他引导加载程序:它正在将自身从软盘加载到内存中。目前,我正在研究 C 函数如何使用 NASM 中定义的符号,我正在为一些我认为“简单”的事情而苦苦挣扎:
我在 NASM 中定义了一个全局变量,它被放置在自定义部分中。这样做的原因是我希望这个变量在> 0xffff800000000000(内核space)范围内有一个虚拟地址。我正在处理我的链接描述文件中的寻址,见下文。该变量在汇编文件中定义如下:
section .kdata
global xyz_foo_bar
xyz_foo_bar:
dq 0
在 C 代码中,我声明了一个仅递增该全局变量的函数:
extern unsigned long xyz_foo_bar;
void test_xyz_inc() {
xyz_foo_bar++;
}
这显然已成功编译和链接。 然而,当我看反汇编函数时,我不明白我看到的是什么。
objdump.exe -M intel -d boot1.elf
...
ffff800000008f73 <test_xyz_inc>:
ffff800000008f73: 55 push rbp
ffff800000008f74: 48 89 e5 mov rbp,rsp
ffff800000008f77: 48 8b 05 00 00 00 00 mov rax,QWORD PTR [rip+0x0] # ffff800000008f7e <test_xyz_inc+0xb>
ffff800000008f7e: 48 8b 00 mov rax,QWORD PTR [rax]
ffff800000008f81: 48 8d 50 01 lea rdx,[rax+0x1]
ffff800000008f85: 48 8b 05 00 00 00 00 mov rax,QWORD PTR [rip+0x0] # ffff800000008f8c <test_xyz_inc+0x19>
ffff800000008f8c: 48 89 10 mov QWORD PTR [rax],rdx
ffff800000008f8f: 90 nop
ffff800000008f90: 5d pop rbp
ffff800000008f91: c3 ret
地址 0xffff800000008f77:当我解释它试图取消对 RIP 的引用而没有位移并将生成的 qword 作为 RAX 的输入时,我是对的吗?它有什么意义?我的猜测是编译器/链接器没有正确计算位移。
下面是我编译代码的方式:
nasm -o boot1.o -l boot1.lst -f elf64 boot1.asm
gcc -ffreestanding -static-pie -c -mabi=sysv -Wall -o c_functions.o c_functions.c
ld -melf_x86_64 --build-id=none -static --unresolved-symbols=report-all -T boot1.ld boot1.o c_functions.o -o boot1.elf
objcopy -O binary boot1.elf boot1.bin
为了完整起见,这里是链接描述文件:
OUTPUT_FORMAT("elf64-x86-64");
/* 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(main);
INCLUDE boot1-vars.ldinc;
SECTIONS
{
. = load_offset;
.text : {
/* Place the code in boot1.o before all other code */
boot1.o(.text);
}
_text_end = .;
. += code_virtaddr;
.ktext : AT(_ktext_physStart) {
_ktext_physStart = . - code_virtaddr;
boot1.o(.ktext);
c_*.o(.text);
}
.kdata : {
boot1.o(.kdata);
}
. -= code_virtaddr;
/* Place the data after the code */
.data : AT(_data_physStart) {
_data_physStart = .;
*(.data);
*(.rodata*);
}
/* Place the uninitialised data in the area after our bootloader
* The BIOS only reads the 512 bytes before this into memory */
.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/ : {
c_*.o(.*);
}
_end = .;
}
我缺少什么基本的东西吗?
PE: boot1-vars.ldinc的内容,按要求:
load_offset = 0x7C00;
load_page = load_offset >> 12;
load_page_expand = load_page << 12;
pages_to_load = ((_end - load_page) >> 12) + 1;
sectors_to_load = ((_end - load_offset) >> 9) + 1;
mmap_special_page = load_page - 1;
mmap_special_page_virtaddr = mmap_special_page << 12;
mmap_special_page_pagetable = load_page - 2;
mmap_special_page_pagetable_virtaddr = mmap_special_page_pagetable << 12;
pmmalloc_special_page = load_page - 3;
pmmalloc_special_page_virtaddr = pmmalloc_special_page << 12;
pmmalloc_special_page_pagetable = load_page - 4;
pmmalloc_special_page_pagetable_virtaddr = pmmalloc_special_page_pagetable << 12;
mm_pml4_rm_segment = (load_page + pages_to_load) << 8;
mm_pml4_offset = 0;
mm_pml4_offset_0 = (mm_pml4_rm_segment << 4) + mm_pml4_offset;
mm_pml4_offset_1003 = mm_pml4_offset_0 + 0x1003;
mm_pml4_offset_2003 = mm_pml4_offset_0 + 0x2003;
mm_pml4_offset_3003 = mm_pml4_offset_0 + 0x3003;
mm_pml4_offset_4007 = mm_pml4_offset_0 + 0x4007;
mm_pml4_offset_5007 = mm_pml4_offset_0 + 0x5007;
mm_pml4_offset_6003 = mm_pml4_offset_0 + 0x6003;
/* kernel_stack_size = 0x2000; */
trap_div0_virtual = trap_div0;
trap_div0_virtual_16 = trap_div0_virtual & 0xffff;
trap_div0_virtual_shr16 = (trap_div0_virtual >> 16) & 0xffff;
trap_div0_virtual_shr32 = trap_div0_virtual >> 32;
trap_doubleFault_virtual = trap_doubleFault;
trap_doubleFault_virtual_16 = trap_doubleFault_virtual & 0xffff;
trap_doubleFault_virtual_shr16 = (trap_doubleFault_virtual >> 16) & 0xffff;
trap_doubleFault_virtual_shr32 = trap_doubleFault_virtual >> 32;
trap_invalidTSS_virtual = trap_invalidTSS;
trap_invalidTSS_virtual_16 = trap_invalidTSS_virtual & 0xffff;
trap_invalidTSS_virtual_shr16 = (trap_invalidTSS_virtual >> 16) & 0xffff;
trap_invalidTSS_virtual_shr32 = trap_invalidTSS_virtual >> 32;
trap_generalProtectionFault_virtual = trap_generalProtectionFault;
trap_generalProtectionFault_virtual_16 = trap_generalProtectionFault_virtual & 0xffff;
trap_generalProtectionFault_virtual_shr16 = (trap_generalProtectionFault_virtual >> 16) & 0xffff;
trap_generalProtectionFault_virtual_shr32 = trap_generalProtectionFault_virtual >> 32;
trap_pageFault_virtual = trap_pageFault;
trap_pageFault_virtual_16 = trap_pageFault_virtual & 0xffff;
trap_pageFault_virtual_shr16 = (trap_pageFault_virtual >> 16) & 0xffff;
trap_pageFault_virtual_shr32 = trap_pageFault_virtual >> 32;
trap_invalidSyscall_virtual = trap_invalidSyscall;
trap_invalidSyscall_virtual_16 = trap_invalidSyscall_virtual & 0xffff;
trap_invalidSyscall_virtual_shr16 = (trap_invalidSyscall_virtual >> 16) & 0xffff;
trap_invalidSyscall_virtual_shr32 = trap_invalidSyscall_virtual >> 32;
isr_spurious_virtual = isr_spurious;
isr_spurious_virtual_16 = isr_spurious_virtual & 0xffff;
isr_spurious_virtual_shr16 = (isr_spurious_virtual >> 16) & 0xffff;
isr_spurious_virtual_shr32 = isr_spurious_virtual >> 32;
isr_dummytmr_virtual = isr_dummytmr;
isr_dummytmr_virtual_16 = isr_dummytmr_virtual & 0xffff;
isr_dummytmr_virtual_shr16 = (isr_dummytmr_virtual >> 16) & 0xffff;
isr_dummytmr_virtual_shr32 = isr_dummytmr_virtual >> 32;
isr_userDummy_virtual = isr_userDummy;
isr_userDummy_virtual_16 = isr_userDummy_virtual & 0xffff;
isr_userDummy_virtual_shr16 = (isr_userDummy_virtual >> 16) & 0xffff;
isr_userDummy_virtual_shr32 = isr_userDummy_virtual >> 32;
tss_virtual = code_virtaddr + TSS;
tss_virtual_16 = tss_virtual & 0xffff;
tss_virtual_shr16_8 = (tss_virtual >> 16) & 0xff;
tss_virtual_shr24_8 = (tss_virtual >> 24) & 0xff;
tss_virtual_shr32 = tss_virtual >> 32;
您正在使用 -static-pie
编译 C 代码。生成的代码将需要一个动态加载程序来填充重定位条目。来自 GCC documentation:
-static-pie
Produce a static position independent executable on targets that support it. A static position independent executable is similar to a static executable, but can be loaded at any address without a dynamic linker. For predictable results, you must also specify the same set of options used for compilation (-fpie, -fPIE, or model suboptions) when you specify this linker option.
由于您最终生成的是二进制文件,因此所有重定位信息都消失了。我可以得出结论,您的引导加载程序不能是动态加载程序。它可能只是将二进制文件直接从磁盘读入内存。
如果您使用 objdump -rd
并查看 test_xyz_inc
,您会发现对 xyz_foo_bar
变量的每次访问都有重定位条目。当代码加载到内存中时,这些值通常由动态加载程序 fixed-up。
您真正想要做的是生成 non-PIC 静态代码。编译 C 文件时,将 -static-pie
替换为 -fno-pic
。我还建议在链接时删除 --unresolved-symbols=report-all
因为我相信你通过包含它来掩盖问题。我还相信您应该确保您没有使用 red-zone 编译内核代码,因此我建议您也使用额外的 GCC 选项 -mno-red-zone
。
举个例子:
gcc -ffreestanding -static-pie -c -mabi=sysv -Wall -o c_functions.o c_functions.c
应该是:
gcc -ffreestanding -fno-pic -mno-red-zone -c -mabi=sysv -Wall -o \
c_functions.o c_functions.c
链接时我建议更改:
ld -melf_x86_64 --build-id=none -static \
--unresolved-symbols=report-all -T boot1.ld boot1.o c_functions.o -o boot1.elf
至:
ld -melf_x86_64 --build-id=none -static -T boot1.ld boot1.o c_functions.o -o boot1.elf
Cygwin 观察结果
在 OP 提到他们将 Cygwin 与 GCC 10.2 一起使用后,我碰巧更新了我的 Cygwin 系统,我可以验证即使将 -static-pie
替换为 -fno-pic
生成的代码是静态的并且将 RIP 的所有位移都设置为 0,并且链接器没有说有任何截断。尝试 -mcmodel=large
没有解决问题。我没有时间调查为什么会发生这种情况,但这是使用 x86-64 或 i386/i686 ELF 交叉编译器进行 OS 开发问题较少的一个很好的理由。我建议在 Cygwin 中构建一个 x86-64 ELF 交叉编译器。 OSDev Wiki 上有通用的 guidelines for building a cross compiler。我没有尝试使用 Cygwin 进行这样的构建,所以我不确定是否有任何障碍使它比 Linux.
故事结束
在听取了@MichaelPetch 的一些建议后,我在 Cygwin 中为 x86_64-elf 目标构建了一个交叉编译器和 binutils。我关注了这些 OSDev Wiki 页面:
该组合似乎工作正常,因为缺少的 RIP-relative 位移设置正确,并且从汇编代码中调用 C 函数不再像以前那样导致一般保护错误.
注意:为了使 binutils 正常工作,我必须按照此处所述修补源代码,否则 gdb 将不想被链接:
非常感谢!