It is fact that ld.so has libc statially linked into it.
Normally we wouldn't need ld.so functionality once the program is
finalyl loaded, but with the next few patches, we will have dlopen which
will reuse the same ld.so functionality.
The problem is that it seams that huge part of the code is possible not
referntially transparent. That is, it is not impossible that some of the
functions have internals states. So when using the struct Linker that is
initialized by ld.so's copy of libc. we must access it using the same
copy even if both copies are identical.
For example in dlopen if you do linker.load_library(..). That would
segfault because it is using the function from libc not ld.so
So I don't truly undestand why should this be needed, but after long
hours of being stuck I thought maybe.. maybe that is the issue and
indeed it turned out to be so.
I attempted fixing this issue before at 43fbaf99. Although it did work,
it worked wrong, and it was just consistently working (but in wrong way)
until it didn't.
Since this is (hopefully) the real fix, I will try to explain exactly
what is going on.
This is explaination by example:
our TLS is memory of size 0x1000 starting at 0x7ffff6c50000,
but the real size is 0x000068 so we have padding stored at master.offset
= 0xf98
Now our symbol looks as follows
Offset Type Sym. Value Name
000000432b20 R_X86_64_DTPOFF64 0000000000000058 errno
The old code did 0x7ffff6c50000 + 0xf98 + 000000432b20 which is
obviosly overflowing the memory and wrong.
The right way 0x7ffff6c50000 + 0xf98 + 0000000000000058.
THe Tls base part and offset are added at __tls_get_addr function.
What is left is storing the 0x58 at the relocation address. The problem
is that we don't have 0x58, but we have (binary base + 0x58) in global
symbol table and binary base so what we store is the (binarybase + 0x58
- binary base).
I hope this does turn out to be wrong.
The problem here was that we alway added the base address, and we
assumed that all addresses we access are relative but this is not the
case in case of non pie binaries. The issue is that all addresses were
base+offset. so if we added the base again it will ofcourse generate
wrong address.
This reverts commit b0dde81c75.
The main issue was not with "access" being used, it was with errno being
accessed. This patch accesses errno as well
LD_LIBRARY_PATH="/folder/with/no/libc" ./a.out
gives segfault with the following stack trace
0x00000000004d1cae in relibc::platform::sys::e (sys=18446744073709551614) at src/platform/linux/mod.rs:54
54 errno = -(sys as isize) as c_int;
(gdb) bt
#0 0x00000000004d1cae in relibc::platform::sys::e (sys=18446744073709551614) at src/platform/linux/mod.rs:54
#1 0x00000000004d361e in <relibc::platform::sys::Sys as relibc::platform::pal::Pal>::open (path=0x5555555634c0, oflag=524288, mode=0) at src/platform/linux/mod.rs:330
#2 0x000000000049a2ad in relibc::fs::File::open (path=0x5555555634c0, oflag=524288) at src/fs.rs:28
#3 0x0000000000482b49 in relibc::ld_so::linker::Linker::load_recursive (self=0x7fffffffdd30, name=..., path=...) at src/ld_so/linker.rs:119
#4 0x0000000000484963 in relibc::ld_so::linker::Linker::load_library (self=0x7fffffffdd30, name=...) at src/ld_so/linker.rs:184
#5 0x0000000000483b53 in relibc::ld_so::linker::Linker::load_data (self=0x7fffffffdd30, name=..., data=...) at src/ld_so/linker.rs:152
#6 0x00000000004831fe in relibc::ld_so::linker::Linker::load_recursive (self=0x7fffffffdd30, name=..., path=...) at src/ld_so/linker.rs:140
#7 0x000000000048228a in relibc::ld_so::linker::Linker::load (self=0x7fffffffdd30, name=..., path=...) at src/ld_so/linker.rs:97
#8 0x0000000000414a3b in relibc_ld_so_start (sp=0x7fffffffe310, ld_entry=4198896) at src/ld_so/start.rs:182
#9 0x0000000000401209 in _start () at src/ld_so/src/lib.rs:10
#10 0x0000000000000001 in ?? ()
#11 0x00007fffffffe592 in ?? ()
#12 0x0000000000000000 in ?? ()
During early parts of ld.so, errno and other thread local variables are
not yet initialized so we cannot use function (such as unistd::access)
that depends on such thread local variables (errno). For this reason
this patch creates small wrapper around the syscall that doesn't not
touch the errno
Current LD_LIBRARY_PATH implementation overwrites the original search
path, which is not the best idea, instead this patch would check
LD_LIBRARY_PATH first and if it didn't find the libraries it is looking
for, then it will search the original search path
There was a bug (also uncovered via binutils) where R_X86_64_DTPOFF64 is
set uncorrectly. This program is the minimal reproducer of the seg fault
#include <errno.h>
int main() {
int oerrno = errno;
}
But it works after the bug fix.
In patch 1182d12006, I mistakingly added
the size of the gap to the total size of the binary, which was not
accurate. As the size of the binary was calculate by subtracting the
upperbound from the lower bound, thus all gaps in the middle are taking
into account.
I noticed that the implementation is noth precise although it worked.
and instead of using the sections to identify memory addresses of
DT_DEBUG. I used segements
Some ELFs can have gaps between the segments, this results in problems
when mprotecting or when converting (addr + size) into rust slice.
Motivated by this This patch will fill these gaps with mmaped addresses.
In theory no real memory should be allocated because mmap is lazy
allocator.
This patch implements ld.so code that makes use of both .init_array and
.fini_array. .init_array is fully utilized and is used in the correct
manner. However .fini_array is not used yet although the function that
runs .fini_array exists
This patch implements tree-based data-structure for catching circular
dependency where libA.so will depen on arbitrarily long chain (including
zero length) of libNs.so one of which will depend on libA.so again. The
main intention of this patch was merely capturing the dependency tree to
prioterize which Elf's .init_array and which .fini_array should run
first, but as a side effect it can capture circular dependencies as well.
At least in linux kernel, assuming that a.out is an elf that is linked
against relibc's own ld.so. When a user attempts `./a.out`, Linux kernel
will map `./a.out`, then map `ld.so` and jump into ld.so entry point.
In relibc ld.so will simply ignore the kernel mapped a.out and create
its own mapping. This patch forces relic ld.so to use the already mapped
`a.out` when ever possible. This would normally have slight performance
improvement (especially that currently relibc doesn't map a.out but
instead copy the data into empty mmaped memory).
The real motivation behind this patch is while impelemnting Runtime
linker debugging protocol for relibc. part of the protocol is ld.so
inseting address of some ld.so managed data structure into .dynamic
seciton of a.out then the debugger would check it there. The thing is
that debuggers have information about the kernel loaded ./a.out and they
check that one specifically which is in our case totally ignored by
relibc.
This patch keep 2 lists, one for strong symbols and one for weak
symbols. First it will check for the symbol to be resolved in the strong
symbols' list, if it is not there it will then check in the weak symbol
list.
This patch does basically two things:
- First make `global` variable not public, And make it accessable via a
function `get_sym`.
- Isolate the procedure that collect global symbols into single function
that does that and call it `collect_syms`.
The motivation of this patch is the second one where this procedure is
extended, thus it needs a seamless way to access those symbols
Introduction:
The original implementation of `relibc_ld_so_start` assumes that
ld.so will always be invoked manually as in "/lib/ld64.so ./a.out"
The problem is regarding this snippet.
if sp.argc < 2 {
eprintln!("ld.so [executable] [arguments...]");
unistd::_exit(1);
loop {}
}
As such, In linux when user types "./a.out" he will recieve the message
ld.so [executable] [arguments...]
This patch makes use of AUXV, specifically AT_ENTRY. When invoking ld.so
manually, AT_ENTRY happens to be the entry point of ld.so. But when
running `./a.out` directly, AT_ENTRY becomes the entry point of `a.out`
this patch compares AT_ENTRY to the entry point of ld.so, if they are
equal only then it will assume that argv[1] is the real program and
adjust the stack, otherwise it will proceed with the stack unadjusted.