-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathtopctx.txt
More file actions
92 lines (67 loc) · 2.93 KB
/
Copy pathtopctx.txt
File metadata and controls
92 lines (67 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
What is struct top* ctx?
~~~~~~~~~~~~~~~~~~~~~~~~
Global variable declared the usual way force at least a page to be allocated
for .bss segment, even though the total size of the variables may be way less
than a whole page. To avoid wasting a whole page, the variables get moved to
the stack. There's always at least one dirty page of stack so no loss happens.
Busybox has struct G which is exactly the same thing.
Normal libc applications generally cannot drop .bss completely, because of
errno (in threadless implementations) or because of other accidentally linked
global stuff. In contrast, lots of tools in minibase lack both .bss and .data
segments.
Unlike .bss/.data, the stuff in the stack cannot be addressed statically.
The workaround is to pass the pointer to every single function that uses
them:
struct top {
int foo;
};
void do_blah(struct top* ctx, ...)
{
ctx->foo++;
}
int main(...) {
struct top context = { ... }, *ctx = &context;
do_blah(ctx, ...);
...
}
This way, first-argument register holds the base address and all globals
are accesses via offsets to that address. The base address is not known
at link time but once set early in main(), it does not change.
(Ab)using TLS for stack-allocated global context
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Reserving a register to point to a global struct -- sounds familiar, right?
That's exactly how thread pointer works. The register is actually reserved,
and the compiler knows it so there is no need to pass it to every single
function:
__thead int foo;
void do_blah(...)
{
foo++;
}
int main(...)
{
set_thread_pointer(alloca(sizeof globals));
do_blah(...);
...
}
Tempting. It's essentially a clean-er version of busybox-ish struct globals.
The problem is, this feature is surprisingly difficult to set up.
* On some arches (x86, MIPS?) setting TP is a privileged operation
that requires a syscall. On others, it's just a register move.
* How the data is offset from TP varies wildly between arches;
sometimes it's TP+offset sometimes TP-offset and almost never
it's just TP.
* Finding out sizeof(globals) requires going through ELF headers;
it is not provided as a linker symbol or anything like that.
* Any initial values (__thread int foo = 1) require going through
ELF headers as well. It all must be done in the init code.
This all makes some sense when applied to actual TLS, but because
the assumption are built into the toolchain, there is no way to adapt
them to much simplier requirements of minitools or busybox.
All in all, so far the problems with TLS setup outweight the minor
inconvenience of having a fixed first argument in certain functions
by a large margin. Maybe this idea warrants a second look, but for now
it remains shelved.
Also, considering the amounts of initialization code, busybox is much
more likely to benefit from all this than minitools, because busybox
would only carry a single copy of the code.