Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lots of stuff worthy of a new release #17

Merged
merged 33 commits into from
Feb 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
494d326
sort symbols in linkmap by address
PoroCYon Aug 5, 2020
daf76fe
resolve IFUNCs correctly
PoroCYon Aug 22, 2020
bfe5fc2
32-bit works again, use smaller opcodes for ifunc checking code
PoroCYon Aug 22, 2020
10181a2
--debugout and linker maps shouldn't mix
PoroCYon Aug 22, 2020
60d51bb
make --debugout output to a separate file in the same run, have debug…
PoroCYon Aug 23, 2020
6af8463
make smoldd work with -fuse-dnload-loader executables (oops)
PoroCYon Aug 24, 2020
ebaa503
*actually* fix smoldd this time
PoroCYon Aug 24, 2020
62406b1
better code arrangement for SKIP_ZERO_VALUE && IFUNC_SUPPORT && USE_D…
PoroCYon Aug 24, 2020
cdfcedc
32-bit ifuncs
PoroCYon Aug 24, 2020
c1601fc
32-bit ifuncs: actually follow the calling convention
PoroCYon Aug 24, 2020
b71b15b
automatically adapt end-of-hashes check width to the symbols used (cl…
PoroCYon Aug 24, 2020
d7135e3
add option to fall back to gen-asm-only behavoir (aka prerelease stuf…
PoroCYon Aug 24, 2020
1e17d11
CRC32C-based hash (thanks Intel) (Python part is still TODO)
PoroCYon Aug 7, 2020
49ac826
Implement crc32c hashing. Add options to smoldd to handle the differe…
blackle Aug 24, 2020
405d8eb
Remove dependency on scanelf by using readelf -s
blackle Aug 24, 2020
8d85d3d
update the README a bit
PoroCYon Aug 24, 2020
181c809
Fix multiple GLIBC versions
blackle Aug 24, 2020
14bc2a5
Merge branch 'crc32c' into master-crc32c-merge
PoroCYon Aug 24, 2020
00b07fe
fix 32-bit crc32c binaries segfaulting, fix smoldd's --hash16 handling
PoroCYon Aug 24, 2020
77bc5bb
[BREAKING] change defaults of a number of arguments
PoroCYon Aug 24, 2020
884d2cd
update README once more
PoroCYon Aug 24, 2020
3e321ca
rearrange arguments a bit so that they make more sense
PoroCYon Aug 24, 2020
2970441
stuff
PoroCYon Aug 24, 2020
2b2efa3
make DWARF info output actually work
PoroCYon Aug 24, 2020
d0291ec
more docs!
PoroCYon Aug 24, 2020
93c83f1
credit where credit is due
PoroCYon Aug 24, 2020
d3d3d1e
fix everything breaking due to 2b2efa3, and also fix library ordening…
PoroCYon Aug 24, 2020
e626847
... and ACTUALLY fix #5
PoroCYon Aug 24, 2020
bfe3ba1
REAL real fix for #5, also fix the hash size check,
PoroCYon Aug 25, 2020
26feb30
fix a syntax error, oops
PoroCYon Aug 25, 2020
6ad8eed
and make --det work again
PoroCYon Aug 25, 2020
e37a0d8
shdr parsing in hackyelf, linkmap parsing bug fixes
PoroCYon Oct 16, 2020
63afc83
keep LD_LIBRARY_PATH order
PoroCYon Jan 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/bin
/obj
__pycache__

smol-20*-*-*/
smol*.tar.xz
18 changes: 9 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ SRCDIR := rt
TESTDIR:= test

NASM ?= nasm
OBJCOPY ?= objcopy

BITS ?= $(shell getconf LONG_BIT)

Expand All @@ -20,8 +21,8 @@ CXXOPTFLAGS=$(COPTFLAGS) -fno-exceptions \
-fno-rtti -fno-enforce-eh-specs -fnothrow-opt -fno-use-cxa-get-exception-ptr \
-fno-implicit-templates -fno-threadsafe-statics -fno-use-cxa-atexit

CFLAGS=-Wall -Wextra -Wpedantic -std=gnu11 -nostartfiles -fno-PIC $(COPTFLAGS) #-DUSE_DL_FINI
CXXFLAGS=-Wall -Wextra -Wpedantic -std=c++11 $(CXXOPTFLAGS) -nostartfiles -fno-PIC
CFLAGS=-g -Wall -Wextra -Wpedantic -std=gnu11 -nostartfiles -fno-PIC $(COPTFLAGS) #-DUSE_DL_FINI
CXXFLAGS=-g -Wall -Wextra -Wpedantic -std=c++11 $(CXXOPTFLAGS) -nostartfiles -fno-PIC

CFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
CXXFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
Expand All @@ -35,13 +36,12 @@ else
CFLAGS += -march=nocona
endif

LIBS = $(filter-out -pthread,$(shell pkg-config --libs sdl2)) -lX11 -lc #-lGL
LIBS = $(filter-out -pthread,$(shell pkg-config --libs sdl2)) -lX11 -lm -lc #-lGL

PWD ?= .

SMOLFLAGS = --smolrt "$(PWD)/rt" --smolld "$(PWD)/ld" \
-falign-stack -fuse-interp \
--verbose #--keeptmp
--verbose -g #--keeptmp
# -fuse-dnload-loader -fskip-zero-value -fuse-nx -fskip-entries -fuse-dt-debug
# -fuse-dl-fini -fno-start-arg -funsafe-dynamic

Expand All @@ -67,12 +67,12 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c $(OBJDIR)/
$(OBJDIR)/%.o: $(TESTDIR)/%.c $(OBJDIR)/
$(CC) $(CFLAGS) -c "$<" -o "$@"

$(BINDIR)/%: $(OBJDIR)/%.o $(BINDIR)/
$(PYTHON3) ./smold.py $(SMOLFLAGS) $(LIBS) "$<" "$@"
$(BINDIR)/%.dbg $(BINDIR)/%: $(OBJDIR)/%.o $(BINDIR)/
$(PYTHON3) ./smold.py --debugout "$@.dbg" $(SMOLFLAGS) --ldflags=-Wl,-Map=$(BINDIR)/$*.map $(LIBS) "$<" "$@"
$(PYTHON3) ./smoltrunc.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"

$(BINDIR)/%-crt: $(OBJDIR)/%.lto.o $(OBJDIR)/crt1.lto.o $(BINDIR)/
$(PYTHON3) ./smold.py $(SMOLFLAGS) --ldflags=-Wl,-Map=$(BINDIR)/$*-crt.map $(LIBS) "$<" $(OBJDIR)/crt1.lto.o "$@"
$(BINDIR)/%-crt.dbg $(BINDIR)/%-crt: $(OBJDIR)/%.lto.o $(OBJDIR)/crt1.lto.o $(BINDIR)/
$(PYTHON3) ./smold.py --debugout "$@.dbg" $(SMOLFLAGS) --ldflags=-Wl,-Map=$(BINDIR)/$*-crt.map $(LIBS) "$<" $(OBJDIR)/crt1.lto.o "$@"
$(PYTHON3) ./smoltrunc.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"

.PHONY: all clean
Expand Down
170 changes: 147 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

Shoddy minsize-oriented linker

PoC by Shiz, bugfixing and 64-bit version by PoroCYon.
PoC and maintenance by Shiz, bugfixing, 64-bit version and maintenance by
PoroCYon, enhancements and bugfixes by blackle.

## Dependencies

* GCC (not clang, as the latter doesn't support `nolto-rel` output), GNU ld,
binutils, GNU make, ...
* nasm 2.13 or newer
* `scanelf` from `pax-utils`
* Python 3

## Usage
Expand All @@ -18,16 +18,22 @@ PoC by Shiz, bugfixing and 64-bit version by PoroCYon.
`.text.startup._start`! Otherwise, the linker script will fail silently, and
the smol startup/symbol resolving code will jump to an undefined location.

***NOTE***: C++ exceptions, RTTI, global *external* variables, thread-local
storage, global constructors and destructors (the ELF `.ctors`/`.dtors`/
`attribute((con-/destructor))` things, not the C++ language constructs), ...
aren't supported yet, and probably won't be anytime soon.

```sh
./smold.py --use_interp --align_stack [--opts...] -lfoo -lbar input.o... output.elf
# example:
./smold.py -fuse-dnload-loader [--opts...] -lfoo -lbar input.o... output.elf
```

```
usage: smold.py [-h] [-m TARGET] [-l LIB] [-L DIR] [-s] [-n] [-d] [-fuse-interp] [-falign-stack] [-fuse-nx]
[-fuse-dnload-loader] [-fskip-zero-value] [-fuse-dt-debug] [-fuse-dl-fini] [-fskip-entries]
[-fno-start-arg] [-funsafe-dynamic] [--nasm NASM] [--cc CC] [--scanelf SCANELF] [--readelf READELF]
[--cflags CFLAGS] [--asflags ASFLAGS] [--ldflags LDFLAGS] [--smolrt SMOLRT] [--smolld SMOLLD]
[--verbose] [--keeptmp]
usage: smold.py [-h] [-m TARGET] [-l LIB] [-L DIR] [-s | -c] [-n] [-d] [-g] [-fuse-interp] [-falign-stack]
[-fskip-zero-value] [-fifunc-support] [-fuse-dnload-loader] [-fuse-nx] [-fuse-dt-debug]
[-fuse-dl-fini] [-fskip-entries] [-fno-start-arg] [-funsafe-dynamic] [-fifunc-strict-cconv]
[--nasm NASM] [--cc CC] [--readelf READELF] [-Wc CFLAGS] [-Wa ASFLAGS] [-Wl LDFLAGS]
[--smolrt SMOLRT] [--smolld SMOLLD] [--gen-rt-only] [--verbose] [--keeptmp] [--debugout DEBUGOUT]
input [input ...] output

positional arguments:
Expand All @@ -41,22 +47,31 @@ optional arguments:
-l LIB, --library LIB
libraries to link against
-L DIR, --libdir DIR directories to search libraries in
-s, --hash16 Use 16-bit (BSD) hashes instead of 32-bit djb2 hashes. Implies -fuse-dnload-loader
-s, --hash16 Use 16-bit (BSD2) hashes instead of 32-bit djb2 hashes. Implies -fuse-dnload-loader. Only
usable for 32-bit output.
-c, --crc32c Use Intel's crc32 intrinsic for hashing. Implies -fuse-dnload-loader. Conflicts with
`--hash16'.
-n, --nx Use NX (i.e. don't use RWE pages). Costs the size of one phdr, plus some extra bytes on
i386.
-d, --det Make the order of imports deterministic (default: just use whatever binutils throws at us)
-fuse-interp Include a program interpreter header (PT_INTERP). If not enabled, ld.so has to be invoked
manually by the end user.
-falign-stack Align the stack before running user code (_start). If not enabled, this has to be done
manually. Costs 1 byte.
-fuse-nx Don't use one big RWE segment, but use separate RW and RE ones. Use this to keep strict
kernels (PaX/grsec) happy. Costs at least the size of one program header entry.
-g, --debug Pass `-g' to the C compiler, assembler and linker. Only useful when `--debugout' is
specified.
-fuse-interp [Default ON] Include a program interpreter header (PT_INTERP). If not enabled, ld.so has to
be invoked manually by the end user. Disable with `-fno-use-interp'.
-falign-stack [Default ON] Align the stack before running user code (_start). If not enabled, this has to
be done manually. Costs 1 byte. Disable with `-fno-align-stack'.
-fskip-zero-value [Default: ON if `-fuse-dnload-loader' supplied, OFF otherwise] Skip an ELF symbol with a
zero address (a weak symbol) when parsing libraries at runtime. Try enabling this if you're
experiencing sudden breakage. However, many libraries don't use weak symbols, so this
doesn't often pose a problem. Costs ~5 bytes.Disable with `-fno-skip-zero-value'.
-fifunc-support [Default ON] Support linking to IFUNCs. Probably needed on x86_64, but costs ~16 bytes.
Ignored on platforms without IFUNC support. Disable with `-fno-fifunc-support'.
-fuse-dnload-loader Use a dnload-style loader for resolving symbols, which doesn't depend on
nonstandard/undocumented ELF and ld.so features, but is slightly larger. If not enabled, a
smaller custom loader is used which assumes glibc.
-fskip-zero-value Skip an ELF symbol with a zero address (a weak symbol) when parsing libraries at runtime.
Try enabling this if you're experiencing sudden breakage. However, many libraries don't use
weak symbols, so this doesn't often pose a problem. Costs ~5 bytes.
smaller custom loader is used which assumes glibc. `-fskip-zero-value' defaults to ON if
this flag is supplied.
-fuse-nx Don't use one big RWE segment, but use separate RW and RE ones. Use this to keep strict
kernels (PaX/grsec) happy. Costs at least the size of one program header entry.
-fuse-dt-debug Use the DT_DEBUG Dyn header to access the link_map, which doesn't depend on
nonstandard/undocumented ELF and ld.so features. If not enabled, the link_map is accessed
using data leaked to the entrypoint by ld.so, which assumes glibc. Costs ~10 bytes.
Expand All @@ -69,17 +84,26 @@ optional arguments:
(envp is a preprequisite for X11, because it needs $DISPLAY.) Frees 3 bytes.
-funsafe-dynamic Don't end the ELF Dyn table with a DT_NULL entry. This might cause ld.so to interpret the
entire binary as the Dyn table, so only enable this if you're sure this won't break things!
-fifunc-strict-cconv On i386, if -fifunc-support is specified, strictly follow the calling convention rules.
Probably not needed, but you never know.
--nasm NASM which nasm binary to use
--cc CC which cc binary to use (MUST BE GCC!)
--scanelf SCANELF which scanelf binary to use
--readelf READELF which readelf binary to use
--cflags CFLAGS Flags to pass to the C compiler for the relinking step
--asflags ASFLAGS Flags to pass to the assembler when creating the ELF header and runtime startup code
--ldflags LDFLAGS Flags to pass to the linker for the final linking step
-Wc CFLAGS, --cflags CFLAGS
Flags to pass to the C compiler for the relinking step
-Wa ASFLAGS, --asflags ASFLAGS
Flags to pass to the assembler when creating the ELF header and runtime startup code
-Wl LDFLAGS, --ldflags LDFLAGS
Flags to pass to the linker for the final linking step
--smolrt SMOLRT Directory containing the smol runtime sources
--smolld SMOLLD Directory containing the smol linker scripts
--gen-rt-only Only generate the headers/runtime assembly source file, instead of doing a full link. (I.e.
fall back to pre-release behavior.)
--verbose Be verbose about what happens and which subcommands are invoked
--keeptmp Keep temp files (only useful for debugging)
--debugout DEBUGOUT Write out an additional, unrunnable debug ELF file with symbol information. (Useful for
debugging with gdb, cannot be ran due to broken relocations.)
--hang-on-startup Hang on startup until a debugger breaks the code out of the loop. Only useful for debugging.
```

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

```
usage: smoldd.py [-h] [--cc CC] [--readelf READELF] [--map MAP] [-s | -c] input

positional arguments:
input input file

optional arguments:
-h, --help show this help message and exit
--cc CC C compiler binary
--readelf READELF readelf binary
--map MAP Get the address of the symbol hash table from the linker map
output instead of attempting to parse the binary.
-s, --hash16 Use 16-bit (BSD2) hashes instead of 32-bit djb2 hashes. Only
usable for 32-bit output.
-c, --crc32c Use Intel's crc32 intrinsic for hashing. Conflicts with `--hash16'.
```

## Debugging your smol-ified executable

So suddenly the output binaries are crashing, while non-smol-ified executables
run just fine. What could've happened?

First of all, it could be PEBCAK: are you compiling with the exact same set of
compiler flags for the optimized and the regular builds? There could always be
a broken codepath in the former.

Secondly, did you enable any of the "evil" flags that can possibly break
compatiblity, such as `-fno-use-interp`, `-fno-align-stack`,
`-fno-skip-zero-value`, `-fno-ifunc-support`, `-fno-start-arg`,
`-funsafe-dynamic`, etc.? Try disabling these first, or try specifying
`-fskip-zero-value`, `-fifunc-strict-cconv`, `-fuse-nx`, `-fuse-dt-debug`,
`-fuse-dl-fini` or `-fuse-dnload-loader` (or remove the last one if you already
were using it). If you had to enable `-fuse-dt-debug` or mess with
`-fuse-dnload-loader`, please file an issue. If you had to specify `-fuse-nx`,
please don't use PaX/grsec for democoding.

But let's assume smol is the cause of the issue here. The first thing you
should do, is to check whether the crash happens in smol's runtime linking
code, or your actual executable code. This can be done by adding an `int3`
(x86) or `bkpt` (ARM) instruction or `__builtin_trap()` intrinsic (GCC/clang)
at the very beginning of your `_start` function. If the binary is now exiting
with a `Trace/breakpoint trap` or `Undefined instruction` error (or something
similar) instead of a `Segmentation fault`, it means the segfault is happening
after smol's runtime linker code has ran.

### The error is happening in the smol runtime linking/startup code

A common source of crashes here is that a symbol actually might not have been
resolved correctly. Try checking the output of `smoldd.py`.

If that isn't the cause, it's time to dig out GDB (see a later section), find
out what roughly is going wrong, and send in an issue ticket.

### The error is happening after the smol runtime linking/startup code

If a segfault is happening here, it's most likely happening when the binary
tries to call an external function. One cause if this can be bad stack
alignment (try messing with `-f[no-]align-stack`, or fix your `_start` code).

Another is that a symbol might have a 'value' (relative address) of zero, which
means the function call turned into a jump to the ELF header of the library,
instead of to the actual function. In this case, try specifying the
`-fskip-zero-value` flag.

Of course, it's still entirely possible it's a yet-unknown calling convention,
reloction, or other issue. If it isn't one of the above known causes, it's yet
again time to dust off your GDB skills and open an issue.

### Attaching GDB to a smol-ified executable

As you might have noticed, GDB cannot run smol-ified executables by itself, as
the ELF headers are too messed up. However, the Linux kernel and glibc dynamic
linker are able to parse it just fine. This means you'll have to attach a live
process, ideally before it segfaults. As racing a below-one-millisecond
timeframe is difficult, there is another solution: specify the `--hang-on-startup`
flag. Then attach your (currently-stuck) process to GDB, increase the program
counter manually to break out of the infinite loop, then continue debugging as
usual.

However, here you don't have any symbols available (let alone DWARF source
info), which makes debugging a bit hard. This can be mitigated by specifying
the `-g` flag, and loading the file specified by the `--debugout` flag into gdb,
which will provide you with symbol and (if `-g` was specified) debugging info.

A quick overview:

```sh
python3 ./smold.py -g --hang-on-startup --debugout=path/to/out.smol.dbg \
[usual args...] input... path/to/out.smol
path/to/out.smol # run it (it will hang)
^Z # background the hung process

# 1. attach the backgrounded process
# 2. break out of the loop (x86_64 example, s/rip/eip/g for i386)
# 3. load symbol and debugging info
gdb -ex "attach $(jobs sp)" \
-ex 'set $rip=$rip+2' \
path/to/out.smol.dbg
```

## Internal workings

`smol.py` inspects the input object files for needed library files and symbols.
Expand Down
54 changes: 0 additions & 54 deletions ld/link.ld

This file was deleted.

Loading