我想知道是否有一种制作“独立于位置的代码”的方法。
想象一下这种情况(这里的实现确实不重要):
您有一个名为'a.c'的文件,其中包含c
代码的两个简单函数-函数a
和函数b
,当函数a
调用函数`b时'..
通过以下方式编译此简单情况时:
gcc -c -o a.out a.c -fPIC
然后观察text
部分,看不到程序集中的函数a
在调用函数b
的地方-保留位为零。
我们都知道,这将使我们置零,将根据重定位表中的值在运行时简单地替换它。
好吧,这是我的问题-我发现它是独立性的一部分。
原因是,因为已经可以在运行之前确定a
和b
之间的距离,所以我可以用相对地址重写包含零的位置,该位置应为b
地址。调用实函数。
我发现这种手动更改字节的方法-在讨论大型程序时很长一段时间。
那么,是否有任何优雅的方法可以通过gcc
/ objdump
中的标志或类似的东西来完成我手动执行的相同操作?
答案 0 :(得分:1)
考虑在Linux上针对x86-64 / AMD64体系结构的独立 hello.c :
/* 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
并使用hello.o
检查结果objdump -x hello.o
时,将同时使用两个本地符号({{1} }),
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
类型的,是相对于32位常量的指令指针。当链接到二进制文件(可执行文件或库文件)时,将应用这些重定位,并且最终的二进制文件将与位置无关。
文件中唯一需要的符号是那些需要从外部访问的符号,R_X86_64_PC32
除外,其地址存储为ELF文件中的起始地址。如果在编译单元外部不需要函数或全局变量,则将其标记为_start
。然后,我们告诉编译器生成位置无关的代码(static
)和位置无关的可执行文件(-fPIC
)。我个人总是启用警告和优化(-pie
),但这取决于您。
因此,OP关于“如何做到这一点” 的问题的答案:
对所有不需要在当前编译单元外部访问的函数和全局变量使用-Wall -O2
。
使用static
-fPIC
编译对象和/或二进制文件。这样可以避免运行时重定位,并在所有支持它的体系结构上使用-pie
相对寻址或类似寻址。
(可选)使用%rip
从二进制文件中删除不需要的符号。这不会影响重定位,但是会通过删除不需要的符号信息来使二进制文件更小。