【问题标题】:MinGW's ld cannot perform PE operations on non PE output fileMinGW的ld不能对非PE输出文件进行PE操作
【发布时间】:2015-09-05 12:32:12
【问题描述】:

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

[BITS 32]

GLOBAL _a

SECTION .text

_a:
    jmp $

然后我制作了这个 C 文件(称为 main.c):

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

为了链接,我使用了这个文件(叫做 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

我已经研究了很长时间,但我仍在努力寻找答案。我希望这不会被标记为重复。我承认存在类似问题,但都有不同的答案,没有一个适合我。

问题:我做错了什么?

【问题讨论】:

    标签: gcc assembly mingw nasm osdev


    【解决方案1】:

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

    也许当前版本也有同样的问题。

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

    --- 编辑 ---

    再次思考这个问题我发现了两个问题:

    正如我已经说过的,某些版本的“ld”在创建“二进制”输出(而不是“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()”之前,甚至可以将“内部”代码放在“a()”和“main()”之前。
    • ...寻址的工作方式略有不同,这意味着如果您做错了什么,将创建大量填充字节(可能在文件开头!)。

    我看到的唯一可能性是创建一个“链接器脚本”(有时称为“链接器命令文件”)并在汇编器代码中创建一个特殊部分(因为我通常使用除“nasm”之外的另一个汇编器,我不知道如果这里的语法是正确的):

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

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

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

    很遗憾,我不是链接器脚本方面的专家,因此我无法为您提供太多帮助。

    使用“-Ttext”也有问题:

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

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

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

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

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

    【讨论】:

    • 你能告诉我你的答案吗?
    • 非常感谢您的回答。我真的认为没有人会回答,因为它不是一个著名的主题,而且周围还有其他类似的问题。
    • @HelderNovais:我编辑了我的答案 - 也许它会帮助你更多。
    • 我得到了一个未定义的 main 引用。 "ld -o exec.e xe c.o asm.o asm.o:test.asm:(.entry+0x1): undefined reference to `main'"
    • 顺便说一句,对我来说最好的解决方案是什么?
    猜你喜欢
    • 2014-09-27
    • 2012-11-25
    • 1970-01-01
    • 1970-01-01
    • 2015-08-15
    • 2017-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多