MinGW 的 ld 无法对非 PE 输出文件执行 PE 操作

MinGW's ld cannot perform PE operations on non PE output file

我知道还有其他一些类似的问题,无论是否是 Whosebug。我为此进行了很多研究,但仍然没有找到单一的解决方案。 我正在做一个操作系统作为一个副项目。我一直在 Assembly 中做所有事情,但现在我想加入 C 代码。 为了测试,我制作了这个汇编代码文件(名为 test.asm):

[BITS 32]

GLOBAL _a

SECTION .text

_a:
    jmp $

然后我做了这个C文件(叫main.c):

extern void a(void);
int main(void)
{
    a();
}

为了link,我使用了这个文件(名为make.bat):

"C:\minGW\bin\gcc.exe"  -ffreestanding -c -o c.o main.c
nasm -f coff -o asm.o test.asm
"C:\minGW\bin\ld.exe" -Ttext 0x100000 --oformat binary -o out.bin c.o asm.o

pause

我已经研究了很多年,但我仍在努力寻找答案。我希望这不会被标记为重复。我承认存在类似的问题,但都有不同的答案,none 对我有用。

问题:我做错了什么?

旧的 MinGW 版本存在 "ld" 根本无法创建非 PE 文件的问题。

可能当前版本存在同样的问题

解决方法是使用 "ld" 创建一个 PE 文件,然后使用 "objcopy" 将 PE 文件转换为二进制、十六进制或 S19。

--- 编辑 ---

再次思考这个问题我看到两个问题:

正如我已经说过的,"ld" 的某些版本在创建 "binary" 输出时存在问题(而不是 "PE"、"ELF" 或使用的任何格式)。

而不是:

ld.exe --oformat binary -o file.bin c.o asm.o

您应该使用以下顺序创建二进制文件:

ld.exe -o file.tmp c.o asm.o
objcopy -O binary file.tmp file.bin

这将创建一个名为 "binary.tmp" 的“.exe”文件;然后 "objcopy" 将从“.exe”文件创建原始数据。

第二个问题是链接本身:

"ld" 采用类似于“.exe”的文件格式——即使输出文件是二进制文件。这意味着 ...

  • ...你甚至无法确定 "main.o" 的目标代码是否真的放在生成的目标代码的首地址。 "ld" 也可以将 "a()" 的代码放在 "main()" 之前,甚至可以将 "internal" 代码放在 "a()" 和 "main()".[=58 之前=]
  • ...寻址的工作方式有点不同,这意味着如果您做错了什么,将会创建大量填充字节(可能在文件的开头!)。

我看到的唯一可能性是创建一个 "linker script"(有时称为 "linker command file")并在汇编代码中创建一个特殊部分(因为我通常使用另一个汇编器而不是 "nasm" 不知道这里的语法对不对):

[BITS 32]
GLOBAL _a
SECTION .entry
    jmp _main
SECTION .text
_a:
    jmp $

在链接描述文件中,您可以指定哪些部分以何种顺序出现。指定“.entry”是文件的第一部分,这样您就可以确定它是文件的第一条指令。

在链接描述文件中,您还可以说多个部分(例如“.entry”、“.text”和“.data”)应该合并为一个部分。这很有用,因为节在 PE 文件中通常是 0x1000 字节对齐的!如果您不将多个部分合并为一个部分,您将在这些部分之间得到很多存根字节!

不幸的是,我不是链接描述文件的专家,所以我不能在这方面帮助你太多。

使用“-Ttext”也有问题:

在 PE 文件中,节的实际地址计算为 "image base" + "relative address"。 “-Ttext”参数只会影响 "relative address"。因为第一部分的 "relative address" 通常在 Windows 中固定为 0x1000,所以“-Ttext 0x2000”只会在第一部分的开头填充 0x1000 存根字节。但是,您根本不会影响“.text”的起始地址 - 您只在“.text”部分的开头填充存根字节,以便第一个 useful 字节位于0x2000。 (也许某些 "ld" 版本的行为有所不同。)

如果您希望文件的第一部分位于地址 0x100000,您应该在链接器脚本中使用等效的“-Ttext 0x1000”(如果使用链接器脚本,则不使用 -Ttext)并定义"image base" 到 0xFF000:

ld.exe -T linkerScript.ld --image-base 0xFF000 -o binary.tmp a.o main.o

“.text”部分的内存地址将为 0xFF000 + 0x1000 = 0x100000。

(而"objcopy"生成的二进制文件的第一个字节将是第一节的第一个字节——代表内存地址0x100000。)