编译glibc时由mipsel-gcc生成的奇怪的加载指令

时间:2015-05-17 10:02:10

标签: c assembly mips glibc gem5

我试图在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进行一些调试后,我发现页面错误是由glibc中的_dl_setup_stack_chk_guard函数触发的。它接受_dl_random函数传递的名为__libc_start_main的void指针,该指针恰好是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。有人可以对此有所了解吗?感谢。

1 个答案:

答案 0 :(得分:1)

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

我认为深入了解the Glibc source code看看到底发生了什么是有帮助的。从_dl_random__libc_start_main开始都可以。

由于_dl_random的值意外为NULL,我们需要找到此变量的初始化方式where it is assigned。在代码分析工具的帮助下,我们发现Glibc中的_dl_random仅在函数_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 arch已经实现了这一点(code)。也许我们可以把它作为一个例子。