在Linux

时间:2018-11-07 16:54:01

标签: c linux gcc

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

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

通过以下方式编译此简单情况时: gcc -c -o a.out a.c -fPIC 然后观察text部分,看不到程序集中的函数a在调用函数b的地方-保留位为零。 我们都知道,这将使我们置零,将根据重定位表中的值在运行时简单地替换它。

好吧,这是我的问题-我发现它是独立性的一部分。 原因是,因为已经可以在运行之前确定ab之间的距离,所以我可以用相对地址重写包含零的位置,该位置应为b地址。调用实函数。 我发现这种手动更改字节的方法-在讨论大型程序时很长一段时间。

那么,是否有任何优雅的方法可以通过gcc / objdump中的标志或类似的东西来完成我手动执行的相同操作?

1 个答案:

答案 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关于“如何做到这一点” 的问题的答案:

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

  2. 使用static -fPIC编译对象和/或二进制文件。这样可以避免运行时重定位,并在所有支持它的体系结构上使用-pie相对寻址或类似寻址。

  3. (可选)使用%rip从二进制文件中删除不需要的符号。这不会影响重定位,但是会通过删除不需要的符号信息来使二进制文件更小。