2

我正在尝试在 Gem 5 模拟器中运行一小段 hello-world MIPS 程序。该程序使用 gcc 4.9.2 和 glibc 2.19(由 crosstool-ng 构建)编译,在 qemu 中运行良好,但在 gem5 中因页面错误(试图访问地址 0)而崩溃。

代码相当简单:

#include <stdio.h>
int main()
{
    printf("hello, world\n");
    return 0;
}

file ./test结果:

./test:ELF 32 位 LSB 可执行文件,MIPS,MIPS-I 版本 1,静态链接,用于 GNU/Linux 3.15.4,未剥离

在使用 gdb 进行一些调试后,我发现页面错误是由_dl_setup_stack_chk_guardglibc 中的函数触发的。它接受一个调用函数_dl_random传递的 void 指针__libc_start_main,它恰好是NULL. 但是,据我所知,这些函数从不取消引用指针,而是生成指令以从内存_dl_random指针指向的加载值。一些代码片段可能有助于理解:

在函数中(未设置__libc_start_main宏):THREAD_SET_STACK_GUARD

   /* Initialize the thread library at least a bit since the libgcc
   functions are using thread functions if these are available and
   we need to setup errno.  */
  __pthread_initialize_minimal ();

  /* Set up the stack checker's canary.  */
  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
# ifdef THREAD_SET_STACK_GUARD
  THREAD_SET_STACK_GUARD (stack_chk_guard);
# else
  __stack_chk_guard = stack_chk_guard;
# endif

在函数中_dl_setup_stack_chk_guard(总是内联):

static inline uintptr_t __attribute__ ((always_inline))
_dl_setup_stack_chk_guard (void *dl_random)
{
  union
  {
    uintptr_t num;
    unsigned char bytes[sizeof (uintptr_t)];
  } ret = { 0 };

  if (dl_random == NULL)
    {
      ret.bytes[sizeof (ret) - 1] = 255;
      ret.bytes[sizeof (ret) - 2] = '\n';
    }
  else
    {
      memcpy (ret.bytes, dl_random, sizeof (ret));
#if BYTE_ORDER == LITTLE_ENDIAN
      ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN
      ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
#else
# error "BYTE_ORDER unknown"
#endif
    }
  return ret.num;
}

反汇编代码:

   0x00400ea4 <+228>:   jal 0x4014b4 <__pthread_initialize_minimal>
   0x00400ea8 <+232>:   nop
   0x00400eac <+236>:   lui v0,0x4a
   0x00400eb0 <+240>:   lw  v0,6232(v0)
   0x00400eb4 <+244>:   li  a0,-256
   0x00400eb8 <+248>:   lwl v1,3(v0)
   0x00400ebc <+252>:   lwr v1,0(v0)
   0x00400ec0 <+256>:   addiu   v0,v0,4
   0x00400ec4 <+260>:   and v1,v1,a0
   0x00400ec8 <+264>:   lui a0,0x4a
   0x00400ecc <+268>:   sw  v1,6228(a0)
  • 0x4a1858 (0x4a0000 + 6232)是地址_dl_random
  • 0x4a1854 (0x4a0000 + 6228)是地址__stack_chk_guard

页面错误发生在0x00400eb8。我不太明白指令是如何生成的0x00400eb80x00400ebc有人可以解释一下吗?谢谢。

4

1 回答 1

1

以下是我如何找到这个问题的根源以及我的解决方案建议。

我认为深入研究 Glibc 源代码以了解实际发生的情况很有帮助。从_dl_random__libc_start_main都可以。

由于 的值_dl_random意外地为 NULL,我们需要找出这个变量是如何初始化的以及它被分配到哪里。借助代码分析工具,我们可以发现_dl_random在 Glibc 中只在 function 中赋值了有意义的值_dl_aux_init,而这个函数是由 调用的__libc_start_min

_dl_aux_init迭代其参数 -- auxvec-- 并对应于auxvec[i].at_type. AT_RANDOM的分配就是这种情况_dl_random。所以问题是没有要分配的AT_RANDOM元素。_dl_random

由于程序在用户态qemu下运行良好,问题的根源在于系统环境提供者,比如gem5,它负责构建auxvec. 有了那个关键字,我们可以发现 是在auxv中构造的gem5/src/arch/<arch-name>/process.cc

MIPS的电流auxv构造如下:

    // Set the system page size
    auxv.push_back(auxv_t(M5_AT_PAGESZ, MipsISA::PageBytes));
    // Set the frequency at which time() increments
    auxv.push_back(auxv_t(M5_AT_CLKTCK, 100));
    // For statically linked executables, this is the virtual
    // address of the program header tables if they appear in the
    // executable image.
    auxv.push_back(auxv_t(M5_AT_PHDR, elfObject->programHeaderTable()));
    DPRINTF(Loader, "auxv at PHDR %08p\n", elfObject->programHeaderTable());
    // This is the size of a program header entry from the elf file.
    auxv.push_back(auxv_t(M5_AT_PHENT, elfObject->programHeaderSize()));
    // This is the number of program headers from the original elf file.
    auxv.push_back(auxv_t(M5_AT_PHNUM, elfObject->programHeaderCount()));
    //The entry point to the program
    auxv.push_back(auxv_t(M5_AT_ENTRY, objFile->entryPoint()));
    //Different user and group IDs
    auxv.push_back(auxv_t(M5_AT_UID, uid()));
    auxv.push_back(auxv_t(M5_AT_EUID, euid()));
    auxv.push_back(auxv_t(M5_AT_GID, gid()));
    auxv.push_back(auxv_t(M5_AT_EGID, egid()));

现在我们知道该怎么做了。我们只需要提供一个可访问的地址值来_dl_random标记为MT_AT_RANDOM. Gem5 的 ARM 架构已经实现了这一点(代码)。也许我们可以举个例子。

于 2016-06-10T13:12:54.030 回答