gpt4 book ai didi

assembly - 如何理解这一点?

转载 作者:行者123 更新时间:2023-12-04 13:54:53 26 4
gpt4 key购买 nike

来自this question

 gcc -c test.s
objcopy -O binary test.o test.bin
test.otest.bin有什么区别?
.text
call start
str:
.string "test\n"
start:
movl $4, %eax
movl $1, %ebx
pop %ecx
movl $5, %edx
int $0x80
ret

以上是做什么的?

最佳答案

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

这为您提供了很多信息。特别是,该文件包含一个名为 .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

这或多或少是您开始使用的程序集。中间的 jejaeor操作码是虚假的:这是 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........|

我们在这里准确地识别了 .texttest.o部分的30个字节。 objcopy -O binary就是这样做的:它提取了文件内容,即唯一的非空部分,即原始操作码本身,删除了所有其他内容,特别是符号表和重定位信息。

重定位是指在给定的代码段中必须更改的内容,以便在存储到内存中的给定位置时可以正确运行。例如,如果代码使用一个变量并希望获得该变量的地址,那么重定位信息将包含一个条目,告知任何实际将代码放入内存的人(通常是链接器):“在代码中,当您知道变量的实际位置时,请写出变量地址”。有趣的是,您显示的代码无需重定位:字节序列可以写入任意内存位置并按原样执行。

让我们看一下代码的功能。
  • call操作码在偏移量0x0b处跳转到mov指令。另外,由于这是call,因此会将返回地址压入堆栈。返回地址是调用完成后应该继续执行的地方,即到达ret操作码时。这是call操作码后的字节地址。在这里,该地址是文字字符串"test\n"的第一个字节的地址。
  • 两个movl分别加载数值为4和1的%eax%ebx
  • pop操作码从堆栈中删除顶部元素,并将其存储在%ecx中。什么是顶级元素?这就是call操作码压入堆栈的地址,即文字字符串第一个字节的地址。
  • 第三个movl加载数值为5的%edx
  • 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;
    }

    (上面的代码不会测试返回值是否有错误,这当然是非常糟糕的。)

    在这里,我使用 mmap()分配了一个内存页面(4096字节),要求我可以在其中读写的页面。 p指向该块。然后,使用 open()read()close(),将 test.bin文件(30个字节)的内容读入该块中。
    mprotect()调用指示内核更改我的页面的访问权限:现在,我将希望能够执行这些字节,即将其视为机器代码。我放弃写入块的权利(取决于确切的内核配置,可能会禁止同时写入和执行页面)。

    含糊的 ((void (*)(void))p)();读为:我取 p;我将其转换为一个不带任何参数且不返回任何内容的函数的指针。我调用该功能。这是将 call放入我的数据块中的C语法。

    当我运行该程序时,我得到:
    $ ./blah
    test

    这是预期的: test.bin中的代码在标准输出上写出 test

    关于assembly - 如何理解这一点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5529599/

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