Commit 1fd08b68 authored by mcgrathr@chromium.org's avatar mcgrathr@chromium.org

Make nacl_helper easily debuggable

This makes it so that attaching gdb to a nacl_helper process, or using gdb
on a core dump from such a process, automatically finds the symbols of the
nacl_helper executable and all the shared libraries it uses.  The theory of
operation and the details are explained in comments in the code.

BUG= http://code.google.com/p/chromium/issues/detail?id=103436
TEST= nacl still works, gdb attach on nacl_helper process, or gdb on core file from one, find symbols

R=mseaborn@chromium.org,bradchen@google.com

Review URL: http://codereview.chromium.org/8491060

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110120 0039d316-1c4b-4281-b951-d872f2087c98
parent 1bb3cc06
......@@ -26,7 +26,12 @@ NaClForkDelegate::NaClForkDelegate()
sandboxed_(false),
fd_(-1) {}
/*
* Note these need to match up with their counterparts in nacl_helper_linux.c
* and nacl_helper_bootstrap_linux.c.
*/
const char kNaClHelperAtZero[] = "--at-zero";
const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX";
void NaClForkDelegate::Init(const bool sandboxed,
const int browserdesc,
......@@ -59,6 +64,7 @@ void NaClForkDelegate::Init(const bool sandboxed,
CommandLine cmd_line(helper_bootstrap_exe);
cmd_line.AppendArgPath(helper_exe);
cmd_line.AppendArgNative(kNaClHelperAtZero);
cmd_line.AppendArgNative(kNaClHelperRDebug);
base::LaunchOptions options;
options.fds_to_remap = &fds_to_map;
options.clone_flags = CLONE_FS | SIGCHLD;
......
......@@ -27,6 +27,18 @@
#define MAX_PHNUM 12
/*
* This exact magic argument string is recognized in check_r_debug_arg, below.
* Requiring the argument to have those Xs as a template both simplifies
* our argument matching code and saves us from having to reformat the
* whole stack to find space for a string longer than the original argument.
*/
#define R_DEBUG_TEMPLATE_PREFIX "--r_debug=0x"
#define R_DEBUG_TEMPLATE_DIGITS "XXXXXXXXXXXXXXXX"
static const char kRDebugTemplate[] =
R_DEBUG_TEMPLATE_PREFIX R_DEBUG_TEMPLATE_DIGITS;
static const size_t kRDebugPrefixLen = sizeof(R_DEBUG_TEMPLATE_PREFIX) - 1;
/*
* We're not using <string.h> functions here, to avoid dependencies.
......@@ -48,6 +60,16 @@ static size_t my_strlen(const char *s) {
return n;
}
static int my_strcmp(const char *a, const char *b) {
while (*a == *b) {
if (*a == '\0')
return 0;
++a;
++b;
}
return (int) (unsigned char) *a - (int) (unsigned char) *b;
}
/*
* Get inline functions for system calls.
......@@ -347,6 +369,35 @@ static ElfW(Addr) load_elf_file(const char *filename,
return ehdr.e_entry + load_bias;
}
/*
* GDB looks for this symbol name when it cannot find PT_DYNAMIC->DT_DEBUG.
* We don't have a PT_DYNAMIC, so it will find this. Now all we have to do
* is arrange for this space to be filled in with the dynamic linker's
* _r_debug contents after they're initialized. That way, attaching GDB to
* this process or examining its core file will find the PIE we loaded, the
* dynamic linker, and all the shared libraries, making debugging pleasant.
*/
struct r_debug _r_debug __attribute__((nocommon, section(".r_debug")));
/*
* If the argument matches the kRDebugTemplate string, then replace
* the 16 Xs with the hexadecimal address of our _r_debug variable.
*/
static int check_r_debug_arg(char *arg) {
if (my_strcmp(arg, kRDebugTemplate) == 0) {
uintptr_t addr = (uintptr_t) &_r_debug;
size_t i = 16;
while (i-- > 0) {
arg[kRDebugPrefixLen + i] = "0123456789abcdef"[addr & 0xf];
addr >>= 4;
}
return 1;
}
return 0;
}
/*
* This is the main loading code. It's called with the starting stack pointer.
* This points to a sequence of pointer-size words:
......@@ -401,6 +452,16 @@ ElfW(Addr) do_load(uintptr_t *stack) {
for (i = 1; i < stack_words; ++i)
stack[i] = stack[i + 1];
/*
* If one of our arguments is the kRDebugTemplate string, then
* we'll modify that argument string in place to specify the
* address of our _r_debug structure.
*/
for (i = 1; i < argc; ++i) {
if (check_r_debug_arg(argv[i]))
break;
}
/*
* Record the auxv entries that are specific to the file loaded.
* The incoming entries point to our own static executable.
......
......@@ -48,6 +48,7 @@ PHDRS {
text PT_LOAD FILEHDR PHDRS;
data PT_LOAD;
reserve PT_LOAD FLAGS(0);
r_debug PT_LOAD;
note PT_NOTE;
stack PT_GNU_STACK FLAGS(6); /* RW, no E */
}
......@@ -111,6 +112,15 @@ SECTIONS {
. = RESERVE_TOP - RESERVE_START;
} :reserve
/*
* This must be placed above the reserved address space, so it won't
* be clobbered by NaCl. We want this to be visible at its fixed address
* in the memory image so the debugger can make sense of things.
*/
.r_debug : {
*(.r_debug)
} :r_debug
/*
* These are empty input sections the linker generates.
* If we don't discard them, they pollute the flags in the output segment.
......
......@@ -7,6 +7,7 @@
#include "chrome/common/nacl_helper_linux.h"
#include <errno.h>
#include <link.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
......@@ -118,6 +119,53 @@ void HandleForkRequest(const std::vector<int>& child_fds) {
} // namespace
static const char kNaClHelperAtZero[] = "at-zero";
static const char kNaClHelperRDebug[] = "r_debug";
/*
* Since we were started by the bootstrap program rather than in the
* usual way, the debugger cannot figure out where our executable
* or the dynamic linker or the shared libraries are in memory,
* so it won't find any symbols. But we can fake it out to find us.
*
* The zygote passes --r_debug=0xXXXXXXXXXXXXXXXX. The bootstrap
* program replaces the Xs with the address of its _r_debug
* structure. The debugger will look for that symbol by name to
* discover the addresses of key dynamic linker data structures.
* Since all it knows about is the original main executable, which
* is the bootstrap program, it finds the symbol defined there. The
* dynamic linker's structure is somewhere else, but it is filled in
* after initialization. The parts that really matter to the
* debugger never change. So we just copy the contents of the
* dynamic linker's structure into the address provided by the option.
* Hereafter, if someone attaches a debugger (or examines a core dump),
* the debugger will find all the symbols in the normal way.
*/
static void check_r_debug(char *argv0) {
std::string r_debug_switch_value =
CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kNaClHelperRDebug);
if (!r_debug_switch_value.empty()) {
char *endp = NULL;
uintptr_t r_debug_addr = strtoul(r_debug_switch_value.c_str(), &endp, 0);
if (r_debug_addr != 0 && *endp == '\0') {
struct r_debug *bootstrap_r_debug = (struct r_debug *) r_debug_addr;
*bootstrap_r_debug = _r_debug;
/*
* Since the main executable (the bootstrap program) does not
* have a dynamic section, the debugger will not skip the
* first element of the link_map list as it usually would for
* an executable or PIE that was loaded normally. But the
* dynamic linker has set l_name for the PIE to "" as is
* normal for the main executable. So the debugger doesn't
* know which file it is. Fill in the actual file name, which
* came in as our argv[0].
*/
struct link_map *l = _r_debug.r_map;
if (l->l_name[0] == '\0')
l->l_name = argv0;
}
}
}
int main(int argc, char *argv[]) {
CommandLine::Init(argc, argv);
......@@ -125,6 +173,8 @@ int main(int argc, char *argv[]) {
base::RandUint64(); // acquire /dev/urandom fd before sandbox is raised
std::vector<int> empty; // for SendMsg() calls
check_r_debug(argv[0]);
g_suid_sandbox_active = (NULL != getenv("SBX_D"));
if (CommandLine::ForCurrentProcess()->HasSwitch(kNaClHelperAtZero)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment