Chromium Code Reviews| Index: third_party/crazy_linker/crazy_linker/src/crazy_linker_rdebug.h |
| diff --git a/third_party/crazy_linker/crazy_linker/src/crazy_linker_rdebug.h b/third_party/crazy_linker/crazy_linker/src/crazy_linker_rdebug.h |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..30ad059871863324445d0f088ca6b71559b2563c |
| --- /dev/null |
| +++ b/third_party/crazy_linker/crazy_linker/src/crazy_linker_rdebug.h |
| @@ -0,0 +1,168 @@ |
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#ifndef CRAZY_LINKER_RDEBUG_H |
| +#define CRAZY_LINKER_RDEBUG_H |
| + |
| +#include <stdint.h> |
| + |
| +// The system linker maintains two lists of libraries at runtime: |
| +// |
| +// - A list that is used by GDB and other tools to search for the |
| +// binaries that are loaded in the process. |
| +// |
| +// This list is accessible by looking at the DT_DEBUG field of the |
| +// dynamic section of any ELF binary loaded by the linker (including |
| +// itself). The field contains the address of a global '_r_debug' |
| +// variable. More on this later. |
| +// |
| +// - A list that is used internally to implement library and symbol |
| +// lookup. The list head and tail are called 'solist' and 'sonext' |
| +// in the linker sources, and their address is unknown (and randomized |
| +// by ASLR), so there is no point trying to change it. |
| +// |
| +// This also means that you cannot call the linker's dlsym() function to |
| +// lookup symbols in libraries that are not loaded through it, i.e. in |
| +// other words, any custom dynamic linker needs its own dlopen() / dlsym() |
| +// and other related functions, and ensure the loaded code only uses its |
| +// own version. |
| +// |
| +// The global '_r_debug' variable is a r_debug structure, which layout |
| +// must be known by GDB, with the following fields: |
| +// |
| +// r_version -> version of the structure (must be 1) |
| +// r_map -> head of a linked list of 'link_map_t' entries, |
| +// one per ELF 'binary' in the process address space. |
| +// r_brk -> pointer to a specific debugging function (see below). |
| +// r_state -> state variable to be read in *r_brk |
| +// r_ldbase -> unused by the system linker, should be 0. (?) |
| +// |
| +// The 'r_brk' field points to an empty function in the system linker |
| +// that is used to notify GDB of changes in the list of shared libraries, |
| +// this works as follows: |
| +// |
| +// - When the linker wants to add a new shared library to the list, |
| +// it first writes RT_ADD to 'r_state', then calls 'r_brk()'. |
| +// |
| +// It modifies the list, then writes RT_CONSISTENT to 'r_state' and |
| +// calls 'r_brk()' again. |
| +// |
| +// - When unloading a library, RT_DELETE + RT_CONSISTENT are used |
| +// instead. |
| +// |
| +// GDB will always place a breakpoint on the function pointed to by |
| +// 'r_brk', and will be able to synchronize with the linker's |
| +// modifications. |
| +// |
| +// The 'r_map' field is a list of nodes with the following structure |
| +// describing each loaded shared library for GDB: |
| +// |
| +// l_addr -> load address of the first PT_LOAD segment in a |
| +// shared library. Note that this is 0 for the linker itself |
| +// and the load-bias for an executable. |
| +// l_name -> name of the executable. This is _always_ a basename!! |
| +// l_ld -> address of the dynamic table for this binary. |
| +// l_next -> pointer to next item in 'r_map' list or NULL. |
| +// l_prev -> pointer to previous item in 'r_map' list. |
| +// |
| +// Note that the system linker ensures that there are always at least |
| +// two items in this list: |
| +// |
| +// - The first item always describes the linker itself, the fields |
| +// actually point to a specially crafted fake entry for it called |
| +// 'libdl_info' in the linker sources. |
| +// |
| +// - The second item describes the executable that was started by |
| +// the kernel. For Android applications, it will always be 'app_process' |
| +// and completely uninteresting. |
| +// |
| +// - Eventually, another entry for VDSOs on platforms that support them. |
| +// |
| +// When implementing a custom linker, being able to debug the process |
| +// unfortunately requires modifying the 'r_map' list to also account |
| +// for libraries loading through it. |
| +// |
| +// One issue with this is that the linker also uses another internal |
| +// variable, called '_r_debut_tail' that points to the last item in |
|
bulach
2013/09/09 16:14:15
nit: _r_debug_tail?
digit1
2013/09/10 09:23:30
Thanks for catching this :-) Done.
|
| +// the list. And there is no way to access it directly. This can lead |
| +// to problems when calling APIs that actually end up using the system's |
| +// own dlopen(). Consider this example: |
| +// |
| +// 1/ Program loads crazy_linker |
| +// |
| +// 2/ Program uses crazy_linker to load libfoo.so, this adds |
| +// a new entry at the end of the '_r_debug.r_map' list, but |
| +// '_r_debug_tail' is unmodified. |
| +// |
| +// 3/ libfoo.so or the Java portion of the program calls a system API |
| +// that ends up loading another library (e.g. libGLESv2_vendor.so), |
| +// this calls the system dlopen(). |
| +// |
| +// 4/ The system dlopen() adds a new entry to the "_r_debug.r_map" |
| +// list by updating the l_next / l_prev fields of the entry pointed |
| +// to by '_r_debug_tail', and this removes 'libfoo.so' from the list! |
| +// |
| +// There is a simple work-around for this issue: Always insert our |
| +// libraries at the _start_ of the 'r_map' list, instead of appending |
| +// them to the end. The system linker doesn't know about custom-loaded |
| +// libraries and thus will never try to unload them. |
| +// |
| +// Note that the linker never uses the 'r_map' list (except or updating |
| +// it for GDB), it only uses 'solist / sonext' to actually perform its |
| +// operations. That's ok if our custom linker completely wraps and |
| +// re-implements these. |
| +namespace crazy { |
| + |
| +struct link_map_t { |
| + uintptr_t l_addr; |
| + char* l_name; |
| + uintptr_t l_ld; |
| + link_map_t* l_next; |
| + link_map_t* l_prev; |
| +}; |
| + |
| +// Values for r_debug->r_state |
| +enum { |
| + RT_CONSISTENT, |
| + RT_ADD, |
| + RT_DELETE |
| +}; |
| + |
| +struct r_debug { |
| + int32_t r_version; |
| + link_map_t* r_map; |
| + void (*r_brk)(void); |
| + int32_t r_state; |
| + uintptr_t r_ldbase; |
| +}; |
| + |
| +class RDebug { |
| +public: |
| + RDebug() : r_debug_(NULL), init_(false), readonly_entries_(false) {} |
| + ~RDebug() {} |
| + |
| + // Add a new entry at the head of the list. |
| + void AddEntry(link_map_t* entry); |
| + |
| + // Remove an entry from the list. |
| + void DelEntry(link_map_t* entry); |
| + |
| + r_debug* GetAddress() { return r_debug_; } |
| + |
| +private: |
| + // Try to find the address of the global _r_debug variable, even |
| + // though there is no symbol for it. Returns true on success. |
| + bool Init(); |
| + |
| + RDebug(const RDebug&); |
| + RDebug& operator=(const RDebug&); |
| + |
| + r_debug* r_debug_; |
| + bool init_; |
| + bool readonly_entries_; |
| +}; |
| + |
| +} // namespace crazy |
| + |
| +#endif // CRAZY_LINKER_REDUG_H |