这是来自这个问题.
gcc -c test.s
objcopy -O binary test.o test.bin
Run Code Online (Sandbox Code Playgroud)
test.o和之间有什么区别test.bin?
.text
call start
str:
.string "test\n"
start:
movl $4, %eax
movl $1, %ebx
pop %ecx
movl $5, %edx
int $0x80
ret
Run Code Online (Sandbox Code Playgroud)
上面做了什么?
Tho*_*nin 11
objcopy -O binary复制源文件的内容.这里test.o是一个"可重定位目标文件":该代码,还有一个符号表和重定位信息,它允许文件与其他文件链接成可执行程序.生成的test.bin文件objcopy只包含代码,没有符号表或重定位信息.这样的"原始"文件对于"普通"编程来说是无用的,但对于具有自己的加载器的代码来说却很方便.
我假设您在32位x86系统上使用Linux.您的test.o文件大小为515个字节.如果您尝试objdump -x test.o获取以下内容,其中描述了test.o目标文件的内容:
$ objdump -x test.o
test.o: file format elf32-i386
test.o
architecture: i386, flags 0x00000010:
HAS_SYMS
start address 0x00000000
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001e 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 00000000 00000000 00000054 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000054 2**2
ALLOC
SYMBOL TABLE:
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
0000000b l .text 00000000 start
00000005 l .text 00000000 str
Run Code Online (Sandbox Code Playgroud)
这为您提供了大量信息.特别是,该文件包含一个名为.text从文件的偏移量0x34开始的部分(十进制为52),长度为0x1e字节(十进制为30).您可以反汇编它以查看操作码本身:
$ objdump -d test.o
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <str-0x5>:
0: e8 06 00 00 00 call b <start>
00000005 <str>:
5: 74 65 je 6c <start+0x61>
7: 73 74 jae 7d <start+0x72>
9: 0a 00 or (%eax),%al
0000000b <start>:
b: b8 04 00 00 00 mov $0x4,%eax
10: bb 01 00 00 00 mov $0x1,%ebx
15: 59 pop %ecx
16: ba 05 00 00 00 mov $0x5,%edx
1b: cd 80 int $0x80
1d: c3 ret
Run Code Online (Sandbox Code Playgroud)
这或多或少是您开始使用的程序集.的je,jae并且or在中间操作码为假:这是objdump试图解释文字串("test\n",导致字节0x74 0x65 0x73 0x64 0X0A为0x00)的操作码.objdump -d还会显示在该.text部分中找到的实际字节,即从偏移量0x34开始的文件中的字节.第一个字节是0xe8 0x06 0x00 ...
现在,看看test.bin文件.它的长度为30个字节.让我们以十六进制的形式看到这些字节:
$ hd test.bin
00000000 e8 06 00 00 00 74 65 73 74 0a 00 b8 04 00 00 00 |.....test.......|
00000010 bb 01 00 00 00 59 ba 05 00 00 00 cd 80 c3 |.....Y........|
Run Code Online (Sandbox Code Playgroud)
我们在这里确切地认识到该.text部分中的30个字节test.o.这就是objcopy -O binary:它提取了文件内容,即唯一的非空部分,即原始操作码本身,删除其他所有内容,特别是符号表和重定位信息.
重定位是指在给定的代码段中必须更改的内容,以便在存储在内存中的给定位置时正常运行.例如,如果代码使用变量并希望获取该变量的地址,那么重定位信息将包含一个条目,告诉谁将实际将代码放入内存(通常是链接器):"在代码中,当你知道变量实际在哪里时,写下变量地址".有趣的是,您显示的代码不需要重定位:字节序列可以写在任意内存位置并按原样执行.
我们来看看代码的作用.
call操作码跳转到mov偏移量0x0B中的指令.此外,由于这是一个call,它会在堆栈上推送返回地址.返回地址是在调用完成后,即ret到达操作码时应继续执行的地址.这是call操作码后面的字节地址.这里,该地址是文字字符串的第一个字节的地址"test\n".movl负载%eax和%ebx数值分别为4和1.pop操作码从栈中删除顶部元件,其存储在%ecx.这个顶级元素是什么?这正是call操作码在堆栈上推送的地址,即文字字符串的第一个字节的地址.movl加载%edx数值为5.int $0x80是32位x86 Linux上的系统调用:这会调用内核.内核将查看寄存器以了解要执行的操作.内核首先查看%eax获取"系统调用号"; 在32位x86上,"4" __NR_write即write()系统调用.此调用需要三个参数,寄存器%ebx,%ecx并%edx按此顺序.这些是目标文件描述符(此处为1:标准输出),指向要写入的数据的指针(此处为文字字符串),以及要写入的数据的长度(此处为5,对应于四个字母和换行符)字符).所以这写"test\n"在标准输出上.ret返回给调用者.ret从堆栈中弹出一个值,然后跳转到该地址.这假定使用call操作码调用此代码块.因此,总而言之,代码test用换行符打印出来.
让我们尝试使用自定义加载器:
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
int
main(void)
{
void *p;
int f;
p = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
f = open("test.bin", O_RDONLY);
read(f, p, 30);
close(f);
mprotect(p, 30, PROT_READ | PROT_EXEC);
((void (*)(void))p)();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
(上面的代码不会测试错误的返回值,当然这非常糟糕.)
在这里,我分配了一页内存(4096字节)mmap(),询问我可以读写的页面.p指向那个块.然后,用open(),read()和close(),我将test.bin文件的内容(30个字节)读入该块.
该mprotect()调用指示内核更改我的页面的访问权限:现在,我希望能够执行这些字节,即将它们视为机器代码.我放弃了写入块的权利(取决于确切的内核配置,有一个可以写入和执行的页面可能被禁止).
隐蔽((void (*)(void))p)();行文从而:我走p; 我把它作为一个指向函数的指针,它不带任何参数并且什么都不返回; 我调用了那个函数.这是用于创建call数据块的C语法.
当我运行该程序时,我得到:
$ ./blah
test
Run Code Online (Sandbox Code Playgroud)
这是预期的:代码在标准输出上test.bin写出test.
.o 是预链接器,.bin 是后链接器。这是一篇关于链接器的维基百科文章: http://en.wikipedia.org/wiki/Linker_ (computing) 我相信你可以从那里得到要点:)