5

第一次海报。CS二年级学生。

我正在探索在 C 源代码->GCC 编译->Linux 执行环境的上下文中在虚拟地址空间的 .data 部分中创建静态变量。

C程序是test.c

int main()
{
   register int i = 0;
   register int sum = 0;
   static int staticVar[10] = {1,2,3,4,5,6,7,8,9,-1};
   Loop: 
        sum = sum + staticVar[i]; //optimized away
    i = i+1;
    if(i != 10)
    goto Loop; 
   return 0;
 }

要求 GDB ' disass /m' 显示没有用于创建 staticVar[] 的代码,因为检查 .s 文件显示该变量驻留在虚拟地址空间的读/写 .data 段中,该段在创建进程时已放置在那里(这个过程是我感兴趣的)。

检查(我虽然它是' readelf -A test.o')的输出,目标文件包含我假设是在数据段中创建数组的程序集。这是 ELF 输出。

(如果你能告诉我是什么命令生成了这个输出,那么奖励。我无法使用 readelf 复制它。我从网站上获取了命令并保存了输出。我不记得是如何生成的)

[剪辑]

    00000000 <staticVar.1359>:
   0:01 00                  add    %eax,(%eax)
   2:00 00                  add    %al,(%eax) 
   4:02 00                  add    (%eax),%al
   6:00 00                  add    %al,(%eax)
   8:03 00                  add    (%eax),%eax
   a:00 00                  add    %al,(%eax)
   c:04 00                  add    $0x0,%al
   e:00 00                  add    %al,(%eax)
  10:05 00 00 00 06         add    $0x6000000,%eax
  15:00 00                  add    %al,(%eax)
  17:00 07                  add    %al,(%edi)
  19:00 00                  add    %al,(%eax)
  1b:00 08                  add    %cl,(%eax)
  1d:00 00                  add    %al,(%eax)
  1f:00 09                  add    %cl,(%ecx)
  21:00 00                  add    %al,(%eax)
  23:00 ff                  add    %bh,%bh
  25:ff                     (bad)  
  26:ff                     (bad)  
  27:ff                     .byte 0xff

[剪辑]

假设(请正确):该程序集存在于可执行文件中,由 load_elf_binary() 或 execve() 启动的一系列函数的某些部分运行。我没有 at&t(基本英特尔)语法知识,但即使直观地我也看不到这些指令如何构建数组。看起来他们只是将寄存器值加在一起。

底线:我想尽可能多地了解这个静态数组的生命周期,特别是构建它的“缺失代码”在哪里以及如何查看它?或者更好的是如何调试(逐步执行)加载程序过程?我曾尝试在 __start_libc 条目(或类似条目)的 main 之前设置断点,但无法确定该区域有任何前景。

附加信息的链接很棒!谢谢你的时间!

4

4 回答 4

4

的初始化程序staticVar存储在.data可执行文件的部分中。使用objdump(例如,我如何在 Linux 上检查 ELF 文件的数据部分的内容?)应该为您的文件显示如下内容:

./test:     file format elf64-x86-64

Contents of section .data:
 00d2c0 00000000 00000000 00000000 00000000  ................
 00d2d0 00000000 00000000 00000000 00000000  ................
 00d2e0 01000000 02000000 03000000 04000000  ................
 00d2f0 05000000 06000000 07000000 08000000  ................
 00d300 09000000 ffffffff 00000000 00000000  ................
 00d310 00000000 00000000 00000000 00000000  ................

可执行文件中的内容直接映射到进程的地址空间,因此不需要任何代码来创建数据。操作的代码staticVar将直接使用内存指针引用内容;例如,对于您发布的循环,gcc -S给了我这个:

  18                .L5:
  19 0013 90            nop
  20                .L2:
  21 0014 4863C3        movslq  %ebx, %rax
  22 0017 8B148500      movl    staticVar.1707(,%rax,4), %edx
  22      000000
  23 001e 8B45F4        movl    -12(%rbp), %eax
  24 0021 01D0          addl    %edx, %eax
  25 0023 8945F4        movl    %eax, -12(%rbp)
  26 0026 83C301        addl    $1, %ebx
  27 0029 83FB0A        cmpl    $10, %ebx
  28 002c 75E5          jne .L5

此静态数组的生命周期将是您的进程的生命周期,类似于全局变量。无论如何,没有构建它的代码。这只是内存中的一些数据。

P/S:您可能需要像这样添加 volatile :否则可能会优化它,因为 sum 的结果值从未使用过。sumvolatile int sum = 0;gcc

于 2013-09-03T01:20:18.723 回答
1

我的可执行格式仅限于不再使用的旧格式,但我很确定 ELF 可执行文件本身中不存在您要查找的代码。

可执行文件定义逐字复制/映射到进程内存的部分。就像您的可执行文件不包含填充其定义的函数的指令流的代码一样,它也不包含填充静态不可执行数据的代码。为此,请记住数据符号和可执行符号之间没有任何根本区别:就存储而言,它们都是数据。

某些语言,例如 C++,将允许您使用动态初始化程序。动态初始化程序在可执行文件的入口点之前运行,并使用在编译时无法推断的信息填充数据符号。在这种情况下,是的,会有代码。但是,静态初始化的符号不需要,它们可以直接复制或映射到进程的地址空间。

staticVar仔细查看数据。暂时忘记说明;如果你把所有的字节放在一起会发生什么?

01 00 00 00
02 00 00 00
03 00 00 00
04 00 00 00
05 00 00 00
06 00 00 00
07 00 00 00
08 00 00 00
09 00 00 00
ff ff ff ff

这是 little-endian 整数序列的十六进制表示,{1, 2, 3, 4, 5, 6, 7, 8, 9, -1}以 4 个字节为一组排列,以便于识别。

于 2013-09-03T01:00:54.950 回答
0

正如 zneak 在目标文件中回答的那样,没有初始化staticVar变量的代码。该.data部分的实际字节直接在test.o文件中。如果您定义一个const变量,默认情况下gcc会将其放入部分中。.rodata

您最有可能通过objdump --disassemble-all file.o. 也许数据的反汇编使您误以为它是真实的代码。对于正常使用,我建议objdump --disassemble file.o只反汇编包含实际机器代码的部分。

您可以通过运行获取详细的相关信息:objdump -xdst test.o

短暂的关于生活staticVar

  1. gcc 将变量(包括其值)放入该.data部分,因为它是静态初始化变量。段的大小只是变量,地址尚未确定。见objdump -xdst test.o
  2. 链接器确定.data节(和其他)的地址,因为目标文件是已知的。在该.data部分中会出现来自其他目标文件的其他变量。见objdump -xdst test
  3. test执行二进制文件时,包含的段直接映射(mmap())到进程的地址空间,因此staticVar直接从test二进制文件中读取 的值(可能在需要时)。然后动态链接ld-linux地图共享库,如libc. 查看cat /proc/$PID/mapspmap $PID何时将进程$PID加载到内存中。

C中可能有变量初始化代码

只需尝试将 staticVar 更改为本地非静态变量(即默认存储在堆栈中):

   int staticVar[10] = {1,2,3,4,5,6,7,8,9,-1};

默认情况下,gcc 会直接在main()函数中生成初始化代码。只是看看objdump -xdst test.o。这是因为堆栈变量是在运行时分配的(因此它们的地址是确定的)。

于 2013-09-03T01:43:59.147 回答
0

只需将其添加到裸机硬件(即微控制器)(您没有二进制加载程序的好处),您将看到将 .bbs 归零并将 .data 部分从 RO FLASH 复制到 RAM 的代码。

该代码将类似于http://repo.or.cz/w/cbaos.git/blob/HEAD:/kernel/init.c#l23

于 2015-03-13T12:53:35.617 回答