为啥使用“ld -e”选项不能更改 ELF 入口点 0x8048000?

Posted

技术标签:

【中文标题】为啥使用“ld -e”选项不能更改 ELF 入口点 0x8048000?【英文标题】:Why is the ELF entry point 0x8048000 not changeable with the "ld -e" option?为什么使用“ld -e”选项不能更改 ELF 入口点 0x8048000? 【发布时间】:2011-12-28 08:01:53 【问题描述】:

跟进Why is the ELF execution entry point virtual address of the form 0x80xxxxx and not zero 0x0? 和Why do virtual memory addresses for linux binaries start at 0x8048000?,为什么我不能让ld 使用与默认ld -e 不同的入口点?

如果我这样做,我会得到一个返回代码为 139 的 segmentation fault,即使是默认入口点附近的地址也是如此。为什么?

编辑:

我会让问题更具体:

        .text
        .globl _start    
_start:
        movl   $0x4,%eax        # eax = code for 'write' system call   
        movl   $1,%ebx          # ebx = file descriptor to standard output
        movl   $message,%ecx    # ecx = pointer to the message
        movl   $13,%edx         # edx = length of the message
        int    $0x80            # make the system call
        movl   $0x0,%ebx        # the status returned by 'exit'
        movl   $0x1,%eax        # eax = code for 'exit' system call
        int    $0x80            # make the system call
        .data
        .globl message
message:        
        .string "Hello world\n" # The message as data

如果我用as program.s -o program.o 编译它,然后用ld -N program.o -o program 静态链接它,readelf -l program 显示0x0000000000400078 作为文本段的VirtAddr0x400078 作为入口点。运行时会打印“Hello world”。

但是,当我尝试与ld -N -e0x400082 -Ttext=0x400082 program.o -o program 链接时(将文本段和入口点移动4 个字节),程序将是killed。使用readelf -l 检查它现在会显示两个不同的LOAD 类型的标题,一个在0x0000000000400082,一个在0x00000000004000b0

当我尝试0x400086 时,一切正常,而且只有一个LOAD 部分。

    这是怎么回事? 哪些内存地址可以选择,哪些不能选择,为什么?

谢谢。

【问题讨论】:

我还可以使用链接描述文件修改入口点:***.com/a/30536800/895245 【参考方案1】:

为什么我不能让 ld 使用与 ld -e 的默认入口点不同的入口点

你当然可以。这个:

int foo(int argc, char *argv[])  return 0; 

gcc main.c -Wl,-e,foo

不起作用,因为执行不是从 main 开始的。它从_start 开始,它从crt0.o(glibc 的一部分)链接而来,并安排诸如动态链接之类的东西正确启动。通过将_start 重定向到foo,您已经绕过了所有需要的 glibc 初始化,因此无法正常工作。

但是,如果您不需要动态链接,并且愿意做 glibc 通常为您做的事情,那么您可以随意命名入口点。示例:

#include <syscall.h>

int foo()

  syscall(SYS_write, 1, "Hello, world\n", 13);
  syscall(SYS_exit, 0);


gcc t.c -static -nostartfiles -Wl,-e,foo && ./a.out
Hello, world

哦,你这个问题的标题与你的实际问题不符(坏主意(TM))。

要回答标题中的问题,您确定可以更改可执行文件的链接地址。默认情况下,您会获得0x8048000 加载地址(仅限32 位;64 位默认为0x400000)。

您可以轻松地将其更改为例如0x80000 通过将-Wl,-Ttext-segment=0x80000 添加到链接行。

更新:

但是,当我尝试链接 ld -N -e0x400082 -Ttext=0x400082 program.o -o 程序(将文本段和入口点移动 4 个字节)时,程序将被终止。

好吧,如果不违反.text 节对齐约束(即4),就不可能将Ttext 分配给0x400082。您必须保持 .text 地址在至少 4 字节边界上对齐(或更改.text 所需的对齐方式)。

当我将起始地址设置为 0x400078、0x40007c、0x400080、0x400084、...、0x400098 并使用 GNU-ld 2.20.1 时,程序运行正常。

但是,当我使用 binutils 的当前 CVS 快照时,该程序适用于 0x400078、0x40007c、0x400088、0x40008c,并在 0x400080、0x400084、0x400090、0x400094、0x400098 时被杀死。这可能是链接器中的错误,或者我违反了其他一些约束(但我不知道是哪个)。

此时,如果您真的感兴趣,我建议您下载 binutils 源代码,构建 ld,并找出导致它创建两个 PT_LOAD 段而不是一个的确切原因。

更新 2:

对具有重叠 LMA 的部分强制使用新分段。

啊!这只是意味着您需要将.data 移开。这使得一个工作可执行文件:

ld -N -o t t.o -e0x400080 -Ttext=0x400080 -Tdata=0x400180

【讨论】:

我更新了我的问题,以更好地说明什么不能按预期工作。 谢谢,很好的回答,我没有考虑对齐。 我使用 git bisect 查找然后在 binutils 2.20 和 2.21 之间进行更改,从而引入了您描述的更改。它被称为“elf.c (_bfd_elf_map_sections_to_segments):为具有重叠 LMA 的部分强制新段。” (repo.or.cz/w/binutils.git/commit/…)

以上是关于为啥使用“ld -e”选项不能更改 ELF 入口点 0x8048000?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在 C# 应用程序中使用泛型类型作为入口点?

在没有虚拟内存的系统上如何处理ELF入口点地址?

Uboot编译为啥没有生成elf格式的文件

使用gcc

如何告诉/强制 GNU ld 将部分/符号放在输出 ELF 文件的特定部分?

ld:入口点(_main)未定义。对于架构 x86_64:Xcode 9