Skip to content
This repository was archived by the owner on Sep 2, 2025. It is now read-only.

Commit 51db8f8

Browse files
committed
reorganize everything in a single invocation (take 2)
1 parent 09acda9 commit 51db8f8

File tree

13 files changed

+358
-212
lines changed

13 files changed

+358
-212
lines changed

Makefile

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
OBJDIR := obj
22
BINDIR := bin
33
SRCDIR := rt
4-
PYDIR := src
5-
LDDIR := ld
64
TESTDIR:= test
75

6+
NASM ?= nasm
7+
88
BITS ?= $(shell getconf LONG_BIT)
99

1010
# -mpreferred-stack-boundary=3 messes up the stack and kills SSE!
@@ -23,58 +23,38 @@ CXXOPTFLAGS=$(COPTFLAGS) -fno-exceptions \
2323
CFLAGS=-Wall -Wextra -Wpedantic -std=gnu11 -nostartfiles -fno-PIC $(COPTFLAGS) #-DUSE_DL_FINI
2424
CXXFLAGS=-Wall -Wextra -Wpedantic -std=c++11 $(CXXOPTFLAGS) -nostartfiles -fno-PIC
2525

26-
ASFLAGS=-I $(SRCDIR)/
27-
LDFLAGS_ :=
26+
CFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
27+
CXXFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
28+
2829
ifeq ($(BITS),32)
2930
# I think prescott is basically nocona but 32-bit only, althought I'm not sure
3031
# if this one is optimal
31-
CFLAGS += -m32 -march=prescott
32-
LDFLAGS += -m32
33-
ASFLAGS += -f elf32
34-
LDFLAGS_ := -m32
32+
CFLAGS += -march=prescott
3533
else
3634
# I've heard nocona gets slightly smaller binaries than core2
37-
CFLAGS += -m64 -march=nocona
38-
LDFLAGS += -m64
39-
ASFLAGS += -f elf64
40-
LDFLAGS_ := -m64
35+
CFLAGS += -march=nocona
4136
endif
42-
LDFLAGS += -nostartfiles -nostdlib
43-
LDFLAGS_ := $(LDFLAGS_) -T $(LDDIR)/link.ld -Wl,--oformat=binary $(LDFLAGS)
4437

45-
CFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
46-
CXXFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
38+
LIBS = $(filter-out -pthread,$(shell pkg-config --libs sdl2)) -lX11 -lc #-lGL
4739

48-
LIBS=-lc
40+
PWD ?= .
4941

50-
SMOLFLAGS +=
51-
ASFLAGS += -DALIGN_STACK -DUSE_INTERP #-DUSE_DNLOAD_LOADER
52-
#-DUSE_DNLOAD_LOADER #-DUSE_DT_DEBUG #-DUSE_DL_FINI #-DNO_START_ARG #-DUNSAFE_DYNAMIC
42+
SMOLFLAGS = --smolrt "$(PWD)/rt" --smolld "$(PWD)/ld" \
43+
-falign-stack -fuse-interp \
44+
--verbose #--keeptmp
45+
# -fuse-dnload-loader -fskip-zero-value -fuse-nx -fskip-entries -fuse-dt-debug
46+
# -fuse-dl-fini -fno-start-arg -funsafe-dynamic
5347

54-
NASM ?= nasm
5548
PYTHON3 ?= python3
5649

5750
all: $(BINDIR)/hello-crt $(BINDIR)/sdl-crt $(BINDIR)/flag $(BINDIR)/hello-_start
5851

59-
LIBS += $(filter-out -pthread,$(shell pkg-config --libs sdl2)) -lX11 #-lGL
60-
6152
clean:
6253
@$(RM) -vrf $(OBJDIR) $(BINDIR)
6354

6455
%/:
6556
@mkdir -vp "$@"
6657

67-
# TODO: handle this in a more graceful and future-proof way!
68-
ifneq ($(findstring (GCC) 9,$(shell $(CC) --version)),)
69-
INCLINKOPT := -flinker-output=nolto-rel
70-
else
71-
ifneq ($(findstring (GCC) 10,$(shell $(CC) --version)),)
72-
INCLINKOPT := -flinker-output=nolto-rel
73-
else
74-
INCLINKOPT :=
75-
endif
76-
endif
77-
7858
.SECONDARY:
7959

8060
$(OBJDIR)/%.lto.o: $(SRCDIR)/%.c $(OBJDIR)/
@@ -87,26 +67,13 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c $(OBJDIR)/
8767
$(OBJDIR)/%.o: $(TESTDIR)/%.c $(OBJDIR)/
8868
$(CC) $(CFLAGS) -c "$<" -o "$@"
8969

90-
$(OBJDIR)/%.start.o: $(OBJDIR)/%.lto.o $(OBJDIR)/crt1.lto.o
91-
$(CC) $(LDFLAGS) -r $(INCLINKOPT) -o "$@" $^
92-
93-
$(OBJDIR)/symbols.%.asm: $(OBJDIR)/%.o
94-
$(PYTHON3) $(PYDIR)/smol.py $(SMOLFLAGS) $(LIBS) "$<" "$@"
95-
96-
$(OBJDIR)/stub.%.o: $(OBJDIR)/symbols.%.asm $(SRCDIR)/header32.asm \
97-
$(SRCDIR)/loader32.asm
98-
$(NASM) $(ASFLAGS) $< -o $@
99-
100-
$(OBJDIR)/stub.%.start.o: $(OBJDIR)/symbols.%.start.asm $(SRCDIR)/header32.asm \
101-
$(SRCDIR)/loader32.asm
102-
$(NASM) $(ASFLAGS) $< -o $@
103-
104-
$(BINDIR)/%: $(OBJDIR)/%.o $(OBJDIR)/stub.%.o $(BINDIR)/
105-
$(CC) -Wl,-Map=$(BINDIR)/$*.map $(LDFLAGS_) $(OBJDIR)/$*.o $(OBJDIR)/stub.$*.o -o "$@"
106-
./rmtrailzero.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"
70+
$(BINDIR)/%: $(OBJDIR)/%.o $(BINDIR)/
71+
$(PYTHON3) ./smold.py $(SMOLFLAGS) $(LIBS) "$<" "$@"
72+
$(PYTHON3) ./smoltrunc.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"
10773

108-
$(BINDIR)/%-crt: $(OBJDIR)/%.start.o $(OBJDIR)/stub.%.start.o $(BINDIR)/
109-
$(CC) -Wl,-Map=$(BINDIR)/$*-crt.map $(LDFLAGS_) $(OBJDIR)/$*.start.o $(OBJDIR)/stub.$*.start.o -o "$@"
74+
$(BINDIR)/%-crt: $(OBJDIR)/%.lto.o $(OBJDIR)/crt1.lto.o $(BINDIR)/
75+
$(PYTHON3) ./smold.py $(SMOLFLAGS) --ldflags=-Wl,-Map=$(BINDIR)/$*-crt.map $(LIBS) "$<" $(OBJDIR)/crt1.lto.o "$@"
76+
$(PYTHON3) ./smoltrunc.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"
11077

11178
.PHONY: all clean
11279

README.md

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,56 +6,33 @@ PoC by Shiz, bugfixing and 64-bit version by PoroCYon.
66

77
## Dependencies
88

9-
* A C compiler (preferably GCC), GNU ld, binutils, GNU make, ...
9+
* GCC (not clang, as the latter doesn't support `nolto-rel` output), GNU ld,
10+
binutils, GNU make, ...
1011
* nasm 2.13 or newer
11-
* scanelf from pax-utils
12+
* `scanelf` from `pax-utils`
1213
* Python 3
1314

1415
## Usage
1516

17+
***NOTE***: Your entrypoint (`_start`) ***must*** be in a section called
18+
`.text.startup._start`! Otherwise, the linker script will fail silently, and
19+
the smol startup/symbol resolving code will jump to an undefined location.
20+
1621
```sh
17-
./smol.py -lfoo -lbar input.o... smol-output.asm
18-
nasm -I src/ [-Doption ...] -o nasm-output.o smol-output.asm
19-
ld -T ld/link.ld --oformat=binary -o output.elf nasm-output.o input.o...
20-
# or cc -T ld/link.ld -Wl,--oformat=binary -o output.elf nasm-output.o input.o...
22+
./smold.py --use_interp --align_stack [--opts...] -lfoo -lbar input.o... output.elf
2123
```
2224

23-
* `USE_INTERP`: Include an interp segment in the output ELF file. If not, the
24-
dynamic linker **must** be invoked *explicitely*! (You probably want to
25-
enable this.) Costs the size of a phdr plus the size of the interp string.
26-
* `ALIGN_STACK`: *64-bit only*: realign the stack so that SSE instructions
27-
won't segfault. Costs 1 byte.
28-
* `USE_NX`: Don't use `RWE` segments at all. Not very well tested. Costs the
29-
size of 1 phdr, plus some extra stuff on `i386`. Don't forget to pass `-n`
30-
to `smol.py` as well.
31-
* `USE_DL_FINI`: keep track of the `_dl_fini` function and pass it to your
32-
`_start`. Costs 2 bytes, plus maybe a few more depending on how it's passed
33-
to `__libc_start_main`.
34-
* `USE_DT_DEBUG`: retrieve the `struct link_map` from the `r_debug` linker
35-
data (which is placed at `DT_DEBUG` at startup) instead of exploiting data
36-
leakage from `_dt_start_user`. Might be more compatible and compressable, but
37-
strictly worse size-wise by 10 (i386) or 3 (x86_64) bytes.
38-
* `SKIP_ENTRIES`: skip the first two entries of the `struct link_map`, which
39-
represent the main binary and the vDSO. Costs around 5 bytes.
40-
* `USE_DNLOAD_LOADER`: use the symbol loading mechanism as used in dnload (i.e.
41-
traverse the symtab of the imported libraries). Slightly larger, but probably
42-
better compressable and more compatible with other libcs and future versions
43-
of glibc.
44-
* `NO_START_ARG`: *don't* pass the stack pointer to `_start` as the first arg.
45-
Will make it unable to read argc/argv/environ, but gives you 3 bytes.
46-
* `SKIP_ZERO_VALUE`: skip a `Sym` with a `st_value` field of `0`. If this isn't
47-
enabled, weak symbols etc. might be imported instead of the real ones,
48-
causing breakage. Many libraries don't have weak symbols at all, though.
49-
Costs 4 (i386) or 5 (x86_64) bytes.
50-
5125
```
52-
usage: smol.py [-h] [-m TARGET] [-l LIB] [-L DIR] [--nasm NASM] [--cc CC]
53-
[--scanelf SCANELF] [--readelf READELF]
54-
input [input ...] output
26+
usage: smold.py [-h] [-m TARGET] [-l LIB] [-L DIR] [-s] [-n] [-d] [-fuse-interp] [-falign-stack] [-fuse-nx]
27+
[-fuse-dnload-loader] [-fskip-zero-value] [-fuse-dt-debug] [-fuse-dl-fini] [-fskip-entries]
28+
[-fno-start-arg] [-funsafe-dynamic] [--nasm NASM] [--cc CC] [--scanelf SCANELF] [--readelf READELF]
29+
[--cflags CFLAGS] [--asflags ASFLAGS] [--ldflags LDFLAGS] [--smolrt SMOLRT] [--smolld SMOLLD]
30+
[--verbose] [--keeptmp]
31+
input [input ...] output
5532
5633
positional arguments:
5734
input input object file
58-
output output nasm file
35+
output output binary
5936
6037
optional arguments:
6138
-h, --help show this help message and exit
@@ -64,14 +41,45 @@ optional arguments:
6441
-l LIB, --library LIB
6542
libraries to link against
6643
-L DIR, --libdir DIR directories to search libraries in
44+
-s, --hash16 Use 16-bit (BSD) hashes instead of 32-bit djb2 hashes. Implies -fuse-dnload-loader
45+
-n, --nx Use NX (i.e. don't use RWE pages). Costs the size of one phdr, plus some extra bytes on
46+
i386.
47+
-d, --det Make the order of imports deterministic (default: just use whatever binutils throws at us)
48+
-fuse-interp Include a program interpreter header (PT_INTERP). If not enabled, ld.so has to be invoked
49+
manually by the end user.
50+
-falign-stack Align the stack before running user code (_start). If not enabled, this has to be done
51+
manually. Costs 1 byte.
52+
-fuse-nx Don't use one big RWE segment, but use separate RW and RE ones. Use this to keep strict
53+
kernels (PaX/grsec) happy. Costs at least the size of one program header entry.
54+
-fuse-dnload-loader Use a dnload-style loader for resolving symbols, which doesn't depend on
55+
nonstandard/undocumented ELF and ld.so features, but is slightly larger. If not enabled, a
56+
smaller custom loader is used which assumes glibc.
57+
-fskip-zero-value Skip an ELF symbol with a zero address (a weak symbol) when parsing libraries at runtime.
58+
Try enabling this if you're experiencing sudden breakage. However, many libraries don't use
59+
weak symbols, so this doesn't often pose a problem. Costs ~5 bytes.
60+
-fuse-dt-debug Use the DT_DEBUG Dyn header to access the link_map, which doesn't depend on
61+
nonstandard/undocumented ELF and ld.so features. If not enabled, the link_map is accessed
62+
using data leaked to the entrypoint by ld.so, which assumes glibc. Costs ~10 bytes.
63+
-fuse-dl-fini Pass _dl_fini to the user entrypoint, which should be done to properly comply with all
64+
standards, but is very often not needed at all. Costs 2 bytes.
65+
-fskip-entries Skip the first two entries in the link map (resp. ld.so and the vDSO). Speeds up symbol
66+
resolving, but costs ~5 bytes.
67+
-fno-start-arg Don't pass a pointer to argc/argv/envp to the entrypoint using the standard calling
68+
convention. This means you need to read these yourself in assembly if you want to use them!
69+
(envp is a preprequisite for X11, because it needs $DISPLAY.) Frees 3 bytes.
70+
-funsafe-dynamic Don't end the ELF Dyn table with a DT_NULL entry. This might cause ld.so to interpret the
71+
entire binary as the Dyn table, so only enable this if you're sure this won't break things!
6772
--nasm NASM which nasm binary to use
68-
--cc CC which cc binary to use
73+
--cc CC which cc binary to use (MUST BE GCC!)
6974
--scanelf SCANELF which scanelf binary to use
7075
--readelf READELF which readelf binary to use
71-
-n, --nx Use NX (i.e. don't use RWE pages). Costs the size of
72-
one phdr, plus some extra bytes on i386. Don't forget
73-
to pass -DUSE_NX to the assembly loader as well!
74-
76+
--cflags CFLAGS Flags to pass to the C compiler for the relinking step
77+
--asflags ASFLAGS Flags to pass to the assembler when creating the ELF header and runtime startup code
78+
--ldflags LDFLAGS Flags to pass to the linker for the final linking step
79+
--smolrt SMOLRT Directory containing the smol runtime sources
80+
--smolld SMOLLD Directory containing the smol linker scripts
81+
--verbose Be verbose about what happens and which subcommands are invoked
82+
--keeptmp Keep temp files (only useful for debugging)
7583
```
7684

7785
A minimal crt (and `_start` funcion) are provided in case you want to use `main`.
@@ -83,9 +91,6 @@ imported by a `smol`-ified binary. This can thus be used to detect user mistakes
8391
during dynamic linking. (Think of it as an equivalent of `ldd`, except that it
8492
also checks whether the imported functions are present as well.)
8593

86-
***NOTE***: `smoldd.py` currently doesn't support 64-bit binaries anymore, as
87-
there's currently no (good) way of retrieving the symbol hash table anymore.
88-
8994
## Internal workings
9095

9196
`smol.py` inspects the input object files for needed library files and symbols.
@@ -99,7 +104,7 @@ works for glibc): on both i386 and x86_64, the linker startup code
99104
(`_dl_start_user`) leaks the global `struct link_map` to the user code:
100105
on i386, a pointer to it is passed directly through `eax`:
101106

102-
```s
107+
```asm
103108
# (eax, edx, ecx, esi) = (_dl_loaded, argc, argv, envp)
104109
movl _rtld_local@GOTOFF(%ebx), %eax
105110
## [ boring stuff... ]

smol/__init__.py

Whitespace-only changes.

smol/cnl.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
import os.path
3+
import subprocess
4+
import sys
5+
6+
from .parse import *
7+
from .shared import eprintf
8+
9+
def cc_relink_objs(verbose, cc_bin, arch, inputs, output, cflags):
10+
archflag = '-m64' if arch == "x86_64" else '-m32'
11+
12+
cctyp, ccver = get_cc_version(cc_bin)
13+
assert cctyp == "gcc", "A GCC compiler is needed for relinking objects!"
14+
relink_arg = "-flinker-output=rel" if ccver < (9,0) else "-flinker-output=nolto-rel"
15+
16+
args = [cc_bin, archflag, '-nostartfiles', '-nostdlib', \
17+
'-r', relink_arg, '-o', output] + cflags + inputs
18+
19+
if verbose: eprintf("cc: %s" % repr(args))
20+
subprocess.check_call(args, stdout=subprocess.DEVNULL)
21+
22+
def nasm_assemble_elfhdr(verbose, nasm_bin, arch, rtdir, intbl, output, asflags):
23+
if rtdir[-1] != '/': rtdir = rtdir + '/'
24+
archflag = 'elf64' if arch == "x86_64" else 'elf32'
25+
26+
args = [nasm_bin, '-I', rtdir, '-f', archflag] + asflags + [intbl, '-o', output]
27+
28+
if verbose: eprintf("nasm: %s" % repr(args))
29+
subprocess.check_call(args, stdout=subprocess.DEVNULL)
30+
31+
def ld_link_final(verbose, cc_bin, arch, lddir, inobjs, output, ldflags):
32+
archflag = '-m64' if arch == "x86_64" else '-m32'
33+
34+
args = [cc_bin, archflag, '-T', lddir+'/link.ld', \
35+
'-Wl,--oformat=binary', '-nostartfiles', '-nostdlib', \
36+
'-o', output] + inobjs + ldflags
37+
38+
if verbose: eprintf("ld: %s" % repr(args))
39+
subprocess.check_call(args, stdout=subprocess.DEVNULL)
40+

src/smolemit.py renamed to smol/emit.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
from collections import OrderedDict
44

5-
from smolshared import *
5+
from .shared import *
66

77
def sort_imports(libraries, hashfn):
88
#eprintf("in: " + str(libraries))
@@ -34,9 +34,8 @@ def output_x86(libraries, nx, h16, outf, det):
3434
for sym, reloc in symrels: usedrelocs.add(reloc)
3535

3636
if not(nx) and 'R_386_PC32' in usedrelocs and 'R_386_GOT32X' in usedrelocs:
37-
eprintf("Using a mix of R_386_PC32 and R_386_GOT32X relocations! "+\
37+
error("Using a mix of R_386_PC32 and R_386_GOT32X relocations! "+\
3838
"Please change a few C compiler flags and recompile your code.")
39-
exit(1)
4039

4140

4241
use_jmp_bytes = not nx and 'R_386_PC32' in usedrelocs
@@ -111,8 +110,7 @@ def output_x86(libraries, nx, h16, outf, det):
111110

112111
def output_amd64(libraries, nx, h16, outf, det):
113112
if h16:
114-
eprintf("--hash16 not supported yet for x86_64 outputs.")
115-
exit(1)
113+
error("--hash16 not supported yet for x86_64 outputs.")
116114

117115
if nx: outf.write('%define USE_NX 1\n')
118116
# if h16: outf.write('%define USE_HASH16 1\n')
@@ -155,8 +153,7 @@ def output_amd64(libraries, nx, h16, outf, det):
155153
for sym, reloc in symrels:
156154
if reloc not in ['R_X86_64_PLT32', 'R_X86_64_GOTPCRELX', \
157155
'R_X86_64_REX_GOTPCRELX', 'R_X86_64_GOTPCREL']:
158-
eprintf('Relocation type ' + reloc + ' of symbol ' + sym + ' unsupported!')
159-
sys.exit(1)
156+
error('Relocation type ' + reloc + ' of symbol ' + sym + ' unsupported!')
160157

161158
if reloc in ['R_X86_64_GOTPCRELX', 'R_X86_64_REX_GOTPCRELX', \
162159
'R_X86_64_GOTPCREL']:
@@ -192,6 +189,5 @@ def output(arch, libraries, nx, h16, outf, det):
192189
if arch == 'i386': output_x86(libraries, nx, h16, outf, det)
193190
elif arch == 'x86_64': output_amd64(libraries, nx, h16, outf, det)
194191
else:
195-
eprintf("E: cannot emit for arch '" + str(arch) + "'")
196-
sys.exit(1)
192+
error("E: cannot emit for arch '" + str(arch) + "'")
197193

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)