gpt4 book ai didi

c - 在 linux 中修改目标文件函数 xref 的优雅方式

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:33:46 27 4
gpt4 key购买 nike

我想知道是否有一种方法可以制作“真正的位置独立代码”。

想象一下这种情况(这里的实现真的不重要。):您有一个名为“a.c”的文件,其中包含 c 代码的两个简单函数 - functin a 和函数 b,当 fnction a 调用函数 `b'..

当编译这个简单的情况时:gcc -c -o a.out a.c -fPIC然后观察 text 部分,我会看到函数 a,在程序集中,在它调用函数 b 的地方 - 一个保持位置零。众所周知,这归零,将根据重定位表中的值在运行时简单地替换。

好吧,这是我的问题 - 我发现它是独立性的不必要部分。原因是,因为在运行之前,我可以确定 ab 之间的距离,因此我可以重写表示 b 的包含零的位置 地址,与实际函数的调用相对。我发现了这种手动更改字节的方法 - 当我们谈论大型程序时它非常长。

那么,有没有什么优雅的方法可以通过 gcc/objdump 或其他东西的标志来完成我手动执行的相同操作?

最佳答案

考虑这个独立的 hello.c,用于 Linux 上的 x86-64/AMD64 架构:

/* Freestanding Hello World example in Linux on x86_64/x86.
* Compile using
* gcc -Wall -O2 -fPIC -pie -march=x86-64 -mtune=generic -m64 -ffreestanding -nostdlib -nostartfiles hello.c -o hello
*/
#define STDOUT_FILENO 1
#define EXIT_SUCCESS 0

#ifndef __x86_64__
#error This program only works on x86_64 architecture!
#endif

#define SYS_write 1
#define SYS_exit 60

#define SYSCALL1_NORET(nr, arg1) \
__asm__ volatile ( "syscall\n\t" \
: \
: "a" (nr), "D" (arg1) \
: "rcx", "r11" )

#define SYSCALL3(retval, nr, arg1, arg2, arg3) \
__asm__ volatile ( "syscall\n\t" \
: "=a" (retval) \
: "a" (nr), "D" (arg1), "S" (arg2), "d" (arg3) \
: "rcx", "r11" )

static void my_exit(int retval)
{
SYSCALL1_NORET(SYS_exit, retval);
}

static int my_write(int fd, const void *data, int len)
{
int retval;

if (fd == -1 || !data || len < 0)
return -1;

SYSCALL3(retval, SYS_write, fd, data, len);

if (retval < 0)
return -1;

return retval;
}

static int my_strlen(const char *str)
{
int len = 0L;

if (!str)
return -1;

while (*str++)
len++;

return len;
}

static int wrout(const char *str)
{
if (str && *str)
return my_write(STDOUT_FILENO, str, my_strlen(str));
else
return 0;
}

void _start(void)
{
const char *msg = "Hello, world!\n";
wrout(msg);
my_exit(EXIT_SUCCESS);
}

请注意,它满足 OP 的场景:_start() 调用 wrout(),后者调用 my_strlen()my_write( )

(为什么是独立的,没有标准 C 库的所有优点?因为标准库不是用 -fPIC-pie 编译的,所以它有动态链接到标准库;这些调用会进行重定位,使我的观点模糊不清。作为独立的,我们得到一个最小的、完整的、可验证的示例,它会产生清晰、明确的结果。)

编译使用

gcc -Wall -O2 -fPIC -pie -march=x86-64 -mtune=generic -m64 -ffreestanding -nostdlib -nostartfiles hello.c -o hello

并使用 ./hello 运行。它打印出“你好,世界!”应该如此。

接下来,使用 objdump -x hello 检查它:

hello:     file format elf64-x86-64
hello
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000000340

Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3
filesz 0x00000000000001f8 memsz 0x00000000000001f8 flags r-x
INTERP off 0x0000000000000238 vaddr 0x0000000000000238 paddr 0x0000000000000238 align 2**0
filesz 0x000000000000001c memsz 0x000000000000001c flags r--
LOAD off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**21
filesz 0x00000000000003d0 memsz 0x00000000000003d0 flags r-x
LOAD off 0x0000000000000f30 vaddr 0x0000000000200f30 paddr 0x0000000000200f30 align 2**21
filesz 0x00000000000000d0 memsz 0x00000000000000d0 flags rw-
DYNAMIC off 0x0000000000000f30 vaddr 0x0000000000200f30 paddr 0x0000000000200f30 align 2**3
filesz 0x00000000000000d0 memsz 0x00000000000000d0 flags rw-
NOTE off 0x0000000000000254 vaddr 0x0000000000000254 paddr 0x0000000000000254 align 2**2
filesz 0x0000000000000024 memsz 0x0000000000000024 flags r--
EH_FRAME off 0x0000000000000388 vaddr 0x0000000000000388 paddr 0x0000000000000388 align 2**2
filesz 0x0000000000000014 memsz 0x0000000000000014 flags r--
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
RELRO off 0x0000000000000f30 vaddr 0x0000000000200f30 paddr 0x0000000000200f30 align 2**0
filesz 0x00000000000000d0 memsz 0x00000000000000d0 flags r--

Dynamic Section:
GNU_HASH 0x0000000000000278
STRTAB 0x0000000000000320
SYMTAB 0x00000000000002a8
STRSZ 0x0000000000000019
SYMENT 0x0000000000000018
DEBUG 0x0000000000000000
FLAGS_1 0x0000000008000000

Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000000238 0000000000000238 00000238 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.gnu.build-id 00000024 0000000000000254 0000000000000254 00000254 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .gnu.hash 00000030 0000000000000278 0000000000000278 00000278 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .dynsym 00000078 00000000000002a8 00000000000002a8 000002a8 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynstr 00000019 0000000000000320 0000000000000320 00000320 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .text 00000037 0000000000000340 0000000000000340 00000340 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
6 .rodata 0000000f 0000000000000377 0000000000000377 00000377 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .eh_frame_hdr 00000014 0000000000000388 0000000000000388 00000388 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .eh_frame 00000030 00000000000003a0 00000000000003a0 000003a0 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
9 .dynamic 000000d0 0000000000200f30 0000000000200f30 00000f30 2**3
CONTENTS, ALLOC, LOAD, DATA
10 .comment 00000035 0000000000000000 0000000000000000 00001000 2**0
CONTENTS, READONLY
SYMBOL TABLE:
0000000000000238 l d .interp 0000000000000000 .interp
0000000000000254 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id
0000000000000278 l d .gnu.hash 0000000000000000 .gnu.hash
00000000000002a8 l d .dynsym 0000000000000000 .dynsym
0000000000000320 l d .dynstr 0000000000000000 .dynstr
0000000000000340 l d .text 0000000000000000 .text
0000000000000377 l d .rodata 0000000000000000 .rodata
0000000000000388 l d .eh_frame_hdr 0000000000000000 .eh_frame_hdr
00000000000003a0 l d .eh_frame 0000000000000000 .eh_frame
0000000000200f30 l d .dynamic 0000000000000000 .dynamic
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 l df *ABS* 0000000000000000 hello.c
0000000000000000 l df *ABS* 0000000000000000
0000000000200f30 l O .dynamic 0000000000000000 _DYNAMIC
0000000000000388 l .eh_frame_hdr 0000000000000000 __GNU_EH_FRAME_HDR
0000000000201000 l O .dynamic 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000340 g F .text 0000000000000037 _start
0000000000201000 g .dynamic 0000000000000000 __bss_start
0000000000201000 g .dynamic 0000000000000000 _edata
0000000000201000 g .dynamic 0000000000000000 _end

它只有基本的符号。您甚至可以剥离它,strip --strip-unneeded hello,之后它就没有任何符号了。 (起始地址在ELF文件中不需要是符号。)看汇编,objdump -d hello,

hello:     file format elf64-x86-64

Disassembly of section .text:

0000000000000340 <_start>:
340: 48 8d 0d 31 00 00 00 lea 0x31(%rip),%rcx # 378 <_start+0x38>
347: 31 d2 xor %edx,%edx
349: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
350: 48 83 c1 01 add $0x1,%rcx
354: 83 c2 01 add $0x1,%edx
357: 80 79 ff 00 cmpb $0x0,-0x1(%rcx)
35b: 75 f3 jne 350 <_start+0x10>
35d: b8 01 00 00 00 mov $0x1,%eax
362: 48 8d 35 0e 00 00 00 lea 0xe(%rip),%rsi # 377 <_start+0x37>
369: 89 c7 mov %eax,%edi
36b: 0f 05 syscall
36d: 31 ff xor %edi,%edi
36f: b8 3c 00 00 00 mov $0x3c,%eax
374: 0f 05 syscall
376: c3 retq

你会看到所有寻址都是相对于 %rip 的,包括条件跳转。例如,75 f3 在下一个操作码 (0xF3 = -13) 开始之前编码 13 个字节的跳转。

如果您忽略优化 (-O2),GCC 会尽力提供帮助并在 ELF 文件中包含 local 符号;您可以使用 strip --strip-unneeded hello 删除它们。

因此,当您在没有优化的情况下编译为目标文件时,gcc -Wall -fPIC -pie -march=x86-64 -mtune=generic -m64 -ffreestanding -nostdlib -nostartfiles -c hello.c,并使用 objdump -x hello.o 检查生成的 hello.o,您将看到两个局部符号(l第二列),

SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 hello.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l F .text 0000000000000016 my_exit
0000000000000016 l F .text 000000000000004e my_write
0000000000000064 l F .text 0000000000000039 my_strlen
000000000000009d l F .text 0000000000000046 wrout
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
00000000000000e3 g F .text 000000000000002c _start

.text有一个搬迁记录,

RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000000000000ee R_X86_64_PC32 .rodata-0x0000000000000004

R_X86_64_PC32类型,是指令指针相关的32位常量。当链接到二进制文件(可执行文件或库)时,将应用这些重定位,最终二进制文件将与位置无关。

文件中唯一需要的符号是那些需要从外部访问的符号,不包括 _start,其地址存储为 ELF 文件中的起始地址。如果编译单元外不需要函数或全局变量,则将它们标记为static。然后,我们告诉编译器生成与位置无关的代码(-fPIC)和与位置无关的可执行文件(-pie)。我个人总是启用警告和优化 (-Wall -O2),但这取决于您。

因此,OP 关于“如何做到这一点” 的问题的答案:

  1. 对不需要在当前编译单元外访问的所有函数和全局变量使用static

  2. 使用 -fPIC -pie 编译对象和/或二进制文件。这应该避免运行时重定位的需要,并在支持它的所有架构上使用 %rip 相对寻址或类似寻址。

  3. 可选地,使用 strip --strip-unneeded 从二进制文件中删除不需要的符号。这不会影响重定位,但会通过删除不需要的符号信息使二进制文件更小。

关于c - 在 linux 中修改目标文件函数 xref 的优雅方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53194179/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com