我试图在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
。我不太清楚如何生成指令0x00400eb8
和0x00400ebc
。有人可以对此有所了解吗?感谢。
答案 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)。也许我们可以把它作为一个例子。