mirror of https://github.com/Shizmob/smol
reorganize everything in a single invocation (take 2)
This commit is contained in:
parent
09acda915d
commit
51db8f8714
83
Makefile
83
Makefile
|
@ -1,10 +1,10 @@
|
|||
OBJDIR := obj
|
||||
BINDIR := bin
|
||||
SRCDIR := rt
|
||||
PYDIR := src
|
||||
LDDIR := ld
|
||||
TESTDIR:= test
|
||||
|
||||
NASM ?= nasm
|
||||
|
||||
BITS ?= $(shell getconf LONG_BIT)
|
||||
|
||||
# -mpreferred-stack-boundary=3 messes up the stack and kills SSE!
|
||||
|
@ -23,58 +23,38 @@ CXXOPTFLAGS=$(COPTFLAGS) -fno-exceptions \
|
|||
CFLAGS=-Wall -Wextra -Wpedantic -std=gnu11 -nostartfiles -fno-PIC $(COPTFLAGS) #-DUSE_DL_FINI
|
||||
CXXFLAGS=-Wall -Wextra -Wpedantic -std=c++11 $(CXXOPTFLAGS) -nostartfiles -fno-PIC
|
||||
|
||||
ASFLAGS=-I $(SRCDIR)/
|
||||
LDFLAGS_ :=
|
||||
ifeq ($(BITS),32)
|
||||
# I think prescott is basically nocona but 32-bit only, althought I'm not sure
|
||||
# if this one is optimal
|
||||
CFLAGS += -m32 -march=prescott
|
||||
LDFLAGS += -m32
|
||||
ASFLAGS += -f elf32
|
||||
LDFLAGS_ := -m32
|
||||
else
|
||||
# I've heard nocona gets slightly smaller binaries than core2
|
||||
CFLAGS += -m64 -march=nocona
|
||||
LDFLAGS += -m64
|
||||
ASFLAGS += -f elf64
|
||||
LDFLAGS_ := -m64
|
||||
endif
|
||||
LDFLAGS += -nostartfiles -nostdlib
|
||||
LDFLAGS_ := $(LDFLAGS_) -T $(LDDIR)/link.ld -Wl,--oformat=binary $(LDFLAGS)
|
||||
|
||||
CFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
|
||||
CXXFLAGS += -m$(BITS) $(shell pkg-config --cflags sdl2)
|
||||
|
||||
LIBS=-lc
|
||||
ifeq ($(BITS),32)
|
||||
# I think prescott is basically nocona but 32-bit only, althought I'm not sure
|
||||
# if this one is optimal
|
||||
CFLAGS += -march=prescott
|
||||
else
|
||||
# I've heard nocona gets slightly smaller binaries than core2
|
||||
CFLAGS += -march=nocona
|
||||
endif
|
||||
|
||||
SMOLFLAGS +=
|
||||
ASFLAGS += -DALIGN_STACK -DUSE_INTERP #-DUSE_DNLOAD_LOADER
|
||||
#-DUSE_DNLOAD_LOADER #-DUSE_DT_DEBUG #-DUSE_DL_FINI #-DNO_START_ARG #-DUNSAFE_DYNAMIC
|
||||
LIBS = $(filter-out -pthread,$(shell pkg-config --libs sdl2)) -lX11 -lc #-lGL
|
||||
|
||||
PWD ?= .
|
||||
|
||||
SMOLFLAGS = --smolrt "$(PWD)/rt" --smolld "$(PWD)/ld" \
|
||||
-falign-stack -fuse-interp \
|
||||
--verbose #--keeptmp
|
||||
# -fuse-dnload-loader -fskip-zero-value -fuse-nx -fskip-entries -fuse-dt-debug
|
||||
# -fuse-dl-fini -fno-start-arg -funsafe-dynamic
|
||||
|
||||
NASM ?= nasm
|
||||
PYTHON3 ?= python3
|
||||
|
||||
all: $(BINDIR)/hello-crt $(BINDIR)/sdl-crt $(BINDIR)/flag $(BINDIR)/hello-_start
|
||||
|
||||
LIBS += $(filter-out -pthread,$(shell pkg-config --libs sdl2)) -lX11 #-lGL
|
||||
|
||||
clean:
|
||||
@$(RM) -vrf $(OBJDIR) $(BINDIR)
|
||||
|
||||
%/:
|
||||
@mkdir -vp "$@"
|
||||
|
||||
# TODO: handle this in a more graceful and future-proof way!
|
||||
ifneq ($(findstring (GCC) 9,$(shell $(CC) --version)),)
|
||||
INCLINKOPT := -flinker-output=nolto-rel
|
||||
else
|
||||
ifneq ($(findstring (GCC) 10,$(shell $(CC) --version)),)
|
||||
INCLINKOPT := -flinker-output=nolto-rel
|
||||
else
|
||||
INCLINKOPT :=
|
||||
endif
|
||||
endif
|
||||
|
||||
.SECONDARY:
|
||||
|
||||
$(OBJDIR)/%.lto.o: $(SRCDIR)/%.c $(OBJDIR)/
|
||||
|
@ -87,26 +67,13 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c $(OBJDIR)/
|
|||
$(OBJDIR)/%.o: $(TESTDIR)/%.c $(OBJDIR)/
|
||||
$(CC) $(CFLAGS) -c "$<" -o "$@"
|
||||
|
||||
$(OBJDIR)/%.start.o: $(OBJDIR)/%.lto.o $(OBJDIR)/crt1.lto.o
|
||||
$(CC) $(LDFLAGS) -r $(INCLINKOPT) -o "$@" $^
|
||||
$(BINDIR)/%: $(OBJDIR)/%.o $(BINDIR)/
|
||||
$(PYTHON3) ./smold.py $(SMOLFLAGS) $(LIBS) "$<" "$@"
|
||||
$(PYTHON3) ./smoltrunc.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"
|
||||
|
||||
$(OBJDIR)/symbols.%.asm: $(OBJDIR)/%.o
|
||||
$(PYTHON3) $(PYDIR)/smol.py $(SMOLFLAGS) $(LIBS) "$<" "$@"
|
||||
|
||||
$(OBJDIR)/stub.%.o: $(OBJDIR)/symbols.%.asm $(SRCDIR)/header32.asm \
|
||||
$(SRCDIR)/loader32.asm
|
||||
$(NASM) $(ASFLAGS) $< -o $@
|
||||
|
||||
$(OBJDIR)/stub.%.start.o: $(OBJDIR)/symbols.%.start.asm $(SRCDIR)/header32.asm \
|
||||
$(SRCDIR)/loader32.asm
|
||||
$(NASM) $(ASFLAGS) $< -o $@
|
||||
|
||||
$(BINDIR)/%: $(OBJDIR)/%.o $(OBJDIR)/stub.%.o $(BINDIR)/
|
||||
$(CC) -Wl,-Map=$(BINDIR)/$*.map $(LDFLAGS_) $(OBJDIR)/$*.o $(OBJDIR)/stub.$*.o -o "$@"
|
||||
./rmtrailzero.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"
|
||||
|
||||
$(BINDIR)/%-crt: $(OBJDIR)/%.start.o $(OBJDIR)/stub.%.start.o $(BINDIR)/
|
||||
$(CC) -Wl,-Map=$(BINDIR)/$*-crt.map $(LDFLAGS_) $(OBJDIR)/$*.start.o $(OBJDIR)/stub.$*.start.o -o "$@"
|
||||
$(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 "$@"
|
||||
$(PYTHON3) ./smoltrunc.py "$@" "$(OBJDIR)/$(notdir $@)" && mv "$(OBJDIR)/$(notdir $@)" "$@" && chmod +x "$@"
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
|
|
99
README.md
99
README.md
|
@ -6,56 +6,33 @@ PoC by Shiz, bugfixing and 64-bit version by PoroCYon.
|
|||
|
||||
## Dependencies
|
||||
|
||||
* A C compiler (preferably GCC), GNU ld, binutils, GNU make, ...
|
||||
* 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
|
||||
* `scanelf` from `pax-utils`
|
||||
* Python 3
|
||||
|
||||
## Usage
|
||||
|
||||
***NOTE***: Your entrypoint (`_start`) ***must*** be in a section called
|
||||
`.text.startup._start`! Otherwise, the linker script will fail silently, and
|
||||
the smol startup/symbol resolving code will jump to an undefined location.
|
||||
|
||||
```sh
|
||||
./smol.py -lfoo -lbar input.o... smol-output.asm
|
||||
nasm -I src/ [-Doption ...] -o nasm-output.o smol-output.asm
|
||||
ld -T ld/link.ld --oformat=binary -o output.elf nasm-output.o input.o...
|
||||
# or cc -T ld/link.ld -Wl,--oformat=binary -o output.elf nasm-output.o input.o...
|
||||
./smold.py --use_interp --align_stack [--opts...] -lfoo -lbar input.o... output.elf
|
||||
```
|
||||
|
||||
* `USE_INTERP`: Include an interp segment in the output ELF file. If not, the
|
||||
dynamic linker **must** be invoked *explicitely*! (You probably want to
|
||||
enable this.) Costs the size of a phdr plus the size of the interp string.
|
||||
* `ALIGN_STACK`: *64-bit only*: realign the stack so that SSE instructions
|
||||
won't segfault. Costs 1 byte.
|
||||
* `USE_NX`: Don't use `RWE` segments at all. Not very well tested. Costs the
|
||||
size of 1 phdr, plus some extra stuff on `i386`. Don't forget to pass `-n`
|
||||
to `smol.py` as well.
|
||||
* `USE_DL_FINI`: keep track of the `_dl_fini` function and pass it to your
|
||||
`_start`. Costs 2 bytes, plus maybe a few more depending on how it's passed
|
||||
to `__libc_start_main`.
|
||||
* `USE_DT_DEBUG`: retrieve the `struct link_map` from the `r_debug` linker
|
||||
data (which is placed at `DT_DEBUG` at startup) instead of exploiting data
|
||||
leakage from `_dt_start_user`. Might be more compatible and compressable, but
|
||||
strictly worse size-wise by 10 (i386) or 3 (x86_64) bytes.
|
||||
* `SKIP_ENTRIES`: skip the first two entries of the `struct link_map`, which
|
||||
represent the main binary and the vDSO. Costs around 5 bytes.
|
||||
* `USE_DNLOAD_LOADER`: use the symbol loading mechanism as used in dnload (i.e.
|
||||
traverse the symtab of the imported libraries). Slightly larger, but probably
|
||||
better compressable and more compatible with other libcs and future versions
|
||||
of glibc.
|
||||
* `NO_START_ARG`: *don't* pass the stack pointer to `_start` as the first arg.
|
||||
Will make it unable to read argc/argv/environ, but gives you 3 bytes.
|
||||
* `SKIP_ZERO_VALUE`: skip a `Sym` with a `st_value` field of `0`. If this isn't
|
||||
enabled, weak symbols etc. might be imported instead of the real ones,
|
||||
causing breakage. Many libraries don't have weak symbols at all, though.
|
||||
Costs 4 (i386) or 5 (x86_64) bytes.
|
||||
|
||||
```
|
||||
usage: smol.py [-h] [-m TARGET] [-l LIB] [-L DIR] [--nasm NASM] [--cc CC]
|
||||
[--scanelf SCANELF] [--readelf READELF]
|
||||
input [input ...] output
|
||||
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]
|
||||
input [input ...] output
|
||||
|
||||
positional arguments:
|
||||
input input object file
|
||||
output output nasm file
|
||||
output output binary
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
|
@ -64,14 +41,45 @@ 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
|
||||
-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.
|
||||
-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.
|
||||
-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.
|
||||
-fuse-dl-fini Pass _dl_fini to the user entrypoint, which should be done to properly comply with all
|
||||
standards, but is very often not needed at all. Costs 2 bytes.
|
||||
-fskip-entries Skip the first two entries in the link map (resp. ld.so and the vDSO). Speeds up symbol
|
||||
resolving, but costs ~5 bytes.
|
||||
-fno-start-arg Don't pass a pointer to argc/argv/envp to the entrypoint using the standard calling
|
||||
convention. This means you need to read these yourself in assembly if you want to use them!
|
||||
(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!
|
||||
--nasm NASM which nasm binary to use
|
||||
--cc CC which cc 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
|
||||
-n, --nx Use NX (i.e. don't use RWE pages). Costs the size of
|
||||
one phdr, plus some extra bytes on i386. Don't forget
|
||||
to pass -DUSE_NX to the assembly loader as well!
|
||||
|
||||
--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
|
||||
--smolrt SMOLRT Directory containing the smol runtime sources
|
||||
--smolld SMOLLD Directory containing the smol linker scripts
|
||||
--verbose Be verbose about what happens and which subcommands are invoked
|
||||
--keeptmp Keep temp files (only useful for debugging)
|
||||
```
|
||||
|
||||
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
|
|||
during dynamic linking. (Think of it as an equivalent of `ldd`, except that it
|
||||
also checks whether the imported functions are present as well.)
|
||||
|
||||
***NOTE***: `smoldd.py` currently doesn't support 64-bit binaries anymore, as
|
||||
there's currently no (good) way of retrieving the symbol hash table anymore.
|
||||
|
||||
## Internal workings
|
||||
|
||||
`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
|
|||
(`_dl_start_user`) leaks the global `struct link_map` to the user code:
|
||||
on i386, a pointer to it is passed directly through `eax`:
|
||||
|
||||
```s
|
||||
```asm
|
||||
# (eax, edx, ecx, esi) = (_dl_loaded, argc, argv, envp)
|
||||
movl _rtld_local@GOTOFF(%ebx), %eax
|
||||
## [ boring stuff... ]
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
import os.path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from .parse import *
|
||||
from .shared import eprintf
|
||||
|
||||
def cc_relink_objs(verbose, cc_bin, arch, inputs, output, cflags):
|
||||
archflag = '-m64' if arch == "x86_64" else '-m32'
|
||||
|
||||
cctyp, ccver = get_cc_version(cc_bin)
|
||||
assert cctyp == "gcc", "A GCC compiler is needed for relinking objects!"
|
||||
relink_arg = "-flinker-output=rel" if ccver < (9,0) else "-flinker-output=nolto-rel"
|
||||
|
||||
args = [cc_bin, archflag, '-nostartfiles', '-nostdlib', \
|
||||
'-r', relink_arg, '-o', output] + cflags + inputs
|
||||
|
||||
if verbose: eprintf("cc: %s" % repr(args))
|
||||
subprocess.check_call(args, stdout=subprocess.DEVNULL)
|
||||
|
||||
def nasm_assemble_elfhdr(verbose, nasm_bin, arch, rtdir, intbl, output, asflags):
|
||||
if rtdir[-1] != '/': rtdir = rtdir + '/'
|
||||
archflag = 'elf64' if arch == "x86_64" else 'elf32'
|
||||
|
||||
args = [nasm_bin, '-I', rtdir, '-f', archflag] + asflags + [intbl, '-o', output]
|
||||
|
||||
if verbose: eprintf("nasm: %s" % repr(args))
|
||||
subprocess.check_call(args, stdout=subprocess.DEVNULL)
|
||||
|
||||
def ld_link_final(verbose, cc_bin, arch, lddir, inobjs, output, ldflags):
|
||||
archflag = '-m64' if arch == "x86_64" else '-m32'
|
||||
|
||||
args = [cc_bin, archflag, '-T', lddir+'/link.ld', \
|
||||
'-Wl,--oformat=binary', '-nostartfiles', '-nostdlib', \
|
||||
'-o', output] + inobjs + ldflags
|
||||
|
||||
if verbose: eprintf("ld: %s" % repr(args))
|
||||
subprocess.check_call(args, stdout=subprocess.DEVNULL)
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
from smolshared import *
|
||||
from .shared import *
|
||||
|
||||
def sort_imports(libraries, hashfn):
|
||||
#eprintf("in: " + str(libraries))
|
||||
|
@ -34,9 +34,8 @@ def output_x86(libraries, nx, h16, outf, det):
|
|||
for sym, reloc in symrels: usedrelocs.add(reloc)
|
||||
|
||||
if not(nx) and 'R_386_PC32' in usedrelocs and 'R_386_GOT32X' in usedrelocs:
|
||||
eprintf("Using a mix of R_386_PC32 and R_386_GOT32X relocations! "+\
|
||||
error("Using a mix of R_386_PC32 and R_386_GOT32X relocations! "+\
|
||||
"Please change a few C compiler flags and recompile your code.")
|
||||
exit(1)
|
||||
|
||||
|
||||
use_jmp_bytes = not nx and 'R_386_PC32' in usedrelocs
|
||||
|
@ -111,8 +110,7 @@ global {name}
|
|||
|
||||
def output_amd64(libraries, nx, h16, outf, det):
|
||||
if h16:
|
||||
eprintf("--hash16 not supported yet for x86_64 outputs.")
|
||||
exit(1)
|
||||
error("--hash16 not supported yet for x86_64 outputs.")
|
||||
|
||||
if nx: outf.write('%define USE_NX 1\n')
|
||||
# if h16: outf.write('%define USE_HASH16 1\n')
|
||||
|
@ -155,8 +153,7 @@ dynamic.end:
|
|||
for sym, reloc in symrels:
|
||||
if reloc not in ['R_X86_64_PLT32', 'R_X86_64_GOTPCRELX', \
|
||||
'R_X86_64_REX_GOTPCRELX', 'R_X86_64_GOTPCREL']:
|
||||
eprintf('Relocation type ' + reloc + ' of symbol ' + sym + ' unsupported!')
|
||||
sys.exit(1)
|
||||
error('Relocation type ' + reloc + ' of symbol ' + sym + ' unsupported!')
|
||||
|
||||
if reloc in ['R_X86_64_GOTPCRELX', 'R_X86_64_REX_GOTPCRELX', \
|
||||
'R_X86_64_GOTPCREL']:
|
||||
|
@ -192,6 +189,5 @@ def output(arch, libraries, nx, h16, outf, det):
|
|||
if arch == 'i386': output_x86(libraries, nx, h16, outf, det)
|
||||
elif arch == 'x86_64': output_amd64(libraries, nx, h16, outf, det)
|
||||
else:
|
||||
eprintf("E: cannot emit for arch '" + str(arch) + "'")
|
||||
sys.exit(1)
|
||||
error("E: cannot emit for arch '" + str(arch) + "'")
|
||||
|
|
@ -5,7 +5,7 @@ import subprocess
|
|||
import struct
|
||||
import sys
|
||||
|
||||
from smolshared import *
|
||||
from .shared import *
|
||||
|
||||
def decide_arch(inpfiles):
|
||||
archs=set({})
|
||||
|
@ -20,8 +20,7 @@ def decide_arch(inpfiles):
|
|||
archs.add(machnum)
|
||||
|
||||
if len(archs) != 1:
|
||||
eprintf("Input files have multiple architectures, can't link this...")
|
||||
sys.exit(1)
|
||||
error("Input files have multiple architectures, can't link this...")
|
||||
|
||||
archn = list(archs)[0]
|
||||
|
||||
|
@ -48,24 +47,41 @@ def build_reloc_typ_table(reo):
|
|||
|
||||
return relocs
|
||||
|
||||
def get_needed_syms(readelf_bin, inpfiles):
|
||||
output = subprocess.check_output([readelf_bin, '-s', '-W']+inpfiles,
|
||||
def has_lto_object(readelf_bin, files):
|
||||
for x in files:
|
||||
with open(x,'rb') as f:
|
||||
if f.read(2) == b'BC': # LLVM bitcode! --> clang -flto
|
||||
return True
|
||||
|
||||
output = subprocess.check_output([readelf_bin, '-s', '-W'] + files,
|
||||
stderr=subprocess.DEVNULL)
|
||||
outrel = subprocess.check_output([readelf_bin, '-r', '-W']+inpfiles,
|
||||
|
||||
curfile = files[0]
|
||||
for entry in output.decode('utf-8').splitlines():
|
||||
stuff = entry.split()
|
||||
if len(stuff)<2: continue
|
||||
if stuff[0] == "File:": curfile = stuff[1]
|
||||
if "__gnu_lto_" in entry or ".gnu.lto" in entry: # assuming nobody uses a symbol called "__gnu_lto_" ...
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_needed_syms(readelf_bin, inpfile):
|
||||
output = subprocess.check_output([readelf_bin, '-s', '-W',inpfile],
|
||||
stderr=subprocess.DEVNULL)
|
||||
outrel = subprocess.check_output([readelf_bin, '-r', '-W',inpfile],
|
||||
stderr=subprocess.DEVNULL)
|
||||
|
||||
relocs = build_reloc_typ_table(outrel)
|
||||
|
||||
curfile = inpfiles[0]
|
||||
curfile = inpfile
|
||||
syms=set({})
|
||||
for entry in output.decode('utf-8').splitlines():
|
||||
stuff = entry.split()
|
||||
if len(stuff)<2: continue
|
||||
if stuff[0] == "File:": curfile = stuff[1]
|
||||
if len(stuff)<8: continue
|
||||
if stuff[7].startswith("__gnu_lto_"): # yikes, an LTO object
|
||||
eprintf("{} is an LTO object file, can't use this!".format(curfile))
|
||||
exit(1)
|
||||
#if stuff[7].startswith("__gnu_lto_"): # yikes, an LTO object
|
||||
# error("{} is an LTO object file, can't use this!".format(curfile))
|
||||
if stuff[4] == "GLOBAL" and stuff[6] == "UND" and len(stuff[7])>0 \
|
||||
and stuff[7] in relocs:
|
||||
syms.add((stuff[7], relocs[stuff[7]]))
|
||||
|
@ -105,6 +121,21 @@ def get_cc_paths(cc_bin):
|
|||
|
||||
return paths
|
||||
|
||||
def get_cc_version(cc_bin):
|
||||
bak = os.environ.copy()
|
||||
os.environ['LANG'] = "C" # DON'T output localized search dirs!
|
||||
output = subprocess.check_output([cc_bin, '--version'],
|
||||
stderr=subprocess.DEVNULL)
|
||||
os.environ = bak
|
||||
|
||||
lines = output.decode('utf-8').splitlines()
|
||||
if "Free Software Foundation" in lines[1]: # GCC
|
||||
verstr = lines[0].split()[-1]
|
||||
return ("gcc", tuple(map(int, verstr.split('.'))))
|
||||
else: # assume clang
|
||||
verstr = lines[0].split()[-1]
|
||||
return ("clang", tuple(map(int, verstr.split('.'))))
|
||||
|
||||
def is_valid_elf(f): # Good Enough(tm)
|
||||
with open(f, 'rb') as ff: return ff.read(4) == b'\x7FELF'
|
||||
|
||||
|
@ -117,8 +148,7 @@ def find_lib(spaths, wanted):
|
|||
#for f in glob.glob(glob.escape(p) + '/lib' + wanted + '.a' ): return f
|
||||
#for f in glob.glob(glob.escape(p) + '/' + wanted + '.a' ): return f
|
||||
|
||||
eprintf("E: couldn't find library '" + wanted + "'.")
|
||||
sys.exit(1)
|
||||
error("E: couldn't find library '" + wanted + "'.")
|
||||
|
||||
def find_libs(spaths, wanted): return map(lambda l: find_lib(spaths, l), wanted)
|
||||
|
|
@ -21,3 +21,7 @@ def hash_djb2(s):
|
|||
|
||||
def eprintf(*args, **kwargs): print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
def error(*args, **kwargs):
|
||||
eprintf(*args, **kwargs)
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import itertools
|
||||
import os, os.path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from smol.shared import *
|
||||
from smol.parse import *
|
||||
from smol.emit import *
|
||||
from smol.cnl import *
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-m', '--target', default='', \
|
||||
help='architecture to generate asm code for (default: auto)')
|
||||
parser.add_argument('-l', '--library', default=[], metavar='LIB', action='append', \
|
||||
help='libraries to link against')
|
||||
parser.add_argument('-L', '--libdir', default=[], metavar='DIR', action='append', \
|
||||
help="directories to search libraries in")
|
||||
|
||||
parser.add_argument('-s', '--hash16', default=False, action='store_true', \
|
||||
help="Use 16-bit (BSD) hashes instead of 32-bit djb2 hashes. "+\
|
||||
"Implies -fuse-dnload-loader")
|
||||
parser.add_argument('-n', '--nx', default=False, action='store_true', \
|
||||
help="Use NX (i.e. don't use RWE pages). Costs the size of one phdr, "+\
|
||||
"plus some extra bytes on i386.")
|
||||
parser.add_argument('-d', '--det', default=False, action='store_true', \
|
||||
help="Make the order of imports deterministic (default: just use " + \
|
||||
"whatever binutils throws at us)")
|
||||
|
||||
parser.add_argument('-fuse-interp', default=False, action='store_true', \
|
||||
help="Include a program interpreter header (PT_INTERP). If not " +\
|
||||
"enabled, ld.so has to be invoked manually by the end user.")
|
||||
parser.add_argument('-falign-stack', default=False, action='store_true', \
|
||||
help="Align the stack before running user code (_start). If not " + \
|
||||
"enabled, this has to be done manually. Costs 1 byte.")
|
||||
parser.add_argument('-fuse-nx', default=False, action='store_true', \
|
||||
help="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.")
|
||||
parser.add_argument('-fuse-dnload-loader', default=False, action='store_true', \
|
||||
help="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.")
|
||||
parser.add_argument('-fskip-zero-value', default=False, action='store_true', \
|
||||
help="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.")
|
||||
parser.add_argument('-fuse-dt-debug', default=False, action='store_true', \
|
||||
help="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.")
|
||||
parser.add_argument('-fuse-dl-fini', default=False, action='store_true', \
|
||||
help="Pass _dl_fini to the user entrypoint, which should be done to "+\
|
||||
"properly comply with all standards, but is very often not "+\
|
||||
"needed at all. Costs 2 bytes.")
|
||||
parser.add_argument('-fskip-entries', default=False, action='store_true', \
|
||||
help="Skip the first two entries in the link map (resp. ld.so and "+\
|
||||
"the vDSO). Speeds up symbol resolving, but costs ~5 bytes.")
|
||||
parser.add_argument('-fno-start-arg', default=False, action='store_true', \
|
||||
help="Don't pass a pointer to argc/argv/envp to the entrypoint using "+\
|
||||
"the standard calling convention. This means you need to read "+\
|
||||
"these yourself in assembly if you want to use them! (envp is "+\
|
||||
"a preprequisite for X11, because it needs $DISPLAY.) Frees 3 bytes.")
|
||||
parser.add_argument('-funsafe-dynamic', default=False, action='store_true', \
|
||||
help="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!")
|
||||
|
||||
parser.add_argument('--nasm', default=os.getenv('NASM') or shutil.which('nasm'), \
|
||||
help="which nasm binary to use")
|
||||
parser.add_argument('--cc', default=os.getenv('CC') or shutil.which('cc'), \
|
||||
help="which cc binary to use (MUST BE GCC!)")
|
||||
parser.add_argument('--scanelf', default=os.getenv('SCANELF') or shutil.which('scanelf'), \
|
||||
help="which scanelf binary to use")
|
||||
parser.add_argument('--readelf', default=os.getenv('READELF') or shutil.which('readelf'), \
|
||||
help="which readelf binary to use")
|
||||
|
||||
parser.add_argument('--cflags', default=[], metavar='CFLAGS', action='append',
|
||||
help="Flags to pass to the C compiler for the relinking step")
|
||||
parser.add_argument('--asflags', default=[], metavar='ASFLAGS', action='append',
|
||||
help="Flags to pass to the assembler when creating the ELF header and runtime startup code")
|
||||
parser.add_argument('--ldflags', default=[], metavar='LDFLAGS', action='append',
|
||||
help="Flags to pass to the linker for the final linking step")
|
||||
parser.add_argument('--smolrt', default=os.getcwd()+"/rt",
|
||||
help="Directory containing the smol runtime sources")
|
||||
parser.add_argument('--smolld', default=os.getcwd()+"/ld",
|
||||
help="Directory containing the smol linker scripts")
|
||||
|
||||
parser.add_argument('--verbose', default=False, action='store_true', \
|
||||
help="Be verbose about what happens and which subcommands are invoked")
|
||||
parser.add_argument('--keeptmp', default=False, action='store_true', \
|
||||
help="Keep temp files (only useful for debugging)")
|
||||
|
||||
parser.add_argument('input', nargs='+', help="input object file")
|
||||
parser.add_argument('output', type=str, help="output binary")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.hash16:
|
||||
args.fuse_dnload_loader = True
|
||||
|
||||
if args.fskip_zero_value: args.asflags.insert(0, "-DSKIP_ZERO_VALUE")
|
||||
if args.fuse_nx: args.asflags.insert(0, "-DUSE_NX")
|
||||
if args.fskip_entries: args.asflags.insert(0, "-DSKIP_ENTRIES")
|
||||
if args.funsafe_dynamic: args.asflags.insert(0, "-DUNSAFE_DYNAMIC")
|
||||
if args.fno_start_arg: args.asflags.insert(0, "-DNO_START_ARG")
|
||||
if args.fuse_dl_fini: args.asflags.insert(0, "-DUSE_DL_FINI")
|
||||
if args.fuse_dt_debug: args.asflags.insert(0, "-DUSE_DT_DEBUG")
|
||||
if args.fuse_dnload_loader: args.asflags.insert(0, "-DUSE_DNLOAD_LOADER")
|
||||
if args.fuse_interp: args.asflags.insert(0, "-DUSE_INTERP")
|
||||
if args.falign_stack: args.asflags.insert(0, "-DALIGN_STACK")
|
||||
|
||||
for x in ['nasm','cc','scanelf','readelf']:
|
||||
val = args.__dict__[x]
|
||||
if val is None or not os.path.isfile(val):
|
||||
error("'" + x + "' binary" + (" " if val is None
|
||||
else " ('" + val + "')") + " not found")
|
||||
|
||||
arch = args.target.tolower() if len(args.target) != 0 else decide_arch(args.input)
|
||||
if arch not in archmagic:
|
||||
error("Unknown/unsupported architecture '" + str(arch) + "'")
|
||||
if args.verbose: eprintf("arch: %s" % arch)
|
||||
|
||||
objinput = None
|
||||
objinputistemp = False
|
||||
tmp_asm_file = tempfile.mkstemp(prefix='smoltab',suffix='.asm',text=True)
|
||||
tmp_asm_fd = tmp_asm_file[0]
|
||||
tmp_asm_file = tmp_asm_file[1]
|
||||
tmp_elf_file = tempfile.mkstemp(prefix='smolout',suffix='.o')
|
||||
os.close(tmp_elf_file[0])
|
||||
tmp_elf_file = tmp_elf_file[1]
|
||||
try:
|
||||
# if >1 input OR input is LTO object:
|
||||
if len(args.input) > 1 or has_lto_object(args.readelf, args.input):
|
||||
fd, objinput = tempfile.mkstemp(prefix='smolin',suffix='.o')
|
||||
os.close(fd)
|
||||
cc_relink_objs(args.verbose, args.cc, arch, args.input, objinput, args.cflags)
|
||||
else: objinput = args.input[0]
|
||||
|
||||
# generate smol hashtab
|
||||
cc_paths = get_cc_paths(args.cc)
|
||||
syms = get_needed_syms(args.readelf, objinput)
|
||||
spaths = args.libdir + cc_paths['libraries']
|
||||
libraries = cc_paths['libraries']
|
||||
libs = list(find_libs(spaths, args.library))
|
||||
if args.verbose: eprintf("libs = " + str(libs))
|
||||
symbols = {}
|
||||
for symbol, reloc in syms:
|
||||
library = find_symbol(args.scanelf, libs, args.library, symbol)
|
||||
if not library:
|
||||
error("could not find symbol: {}".format(symbol))
|
||||
symbols.setdefault(library, [])
|
||||
symbols[library].append((symbol, reloc))
|
||||
|
||||
with os.fdopen(tmp_asm_fd, mode='w') as taf:
|
||||
output(arch, symbols, args.nx, args.hash16, taf, args.det)
|
||||
if args.verbose:
|
||||
eprintf("wrote symtab to %s" % tmp_asm_file)
|
||||
|
||||
# assemble hash table/ELF header
|
||||
nasm_assemble_elfhdr(args.verbose, args.nasm, arch, args.smolrt,
|
||||
tmp_asm_file, tmp_elf_file, args.asflags)
|
||||
|
||||
# link with LD into the final executable, w/ special linker script
|
||||
ld_link_final(args.verbose, args.cc, arch, args.smolld, [objinput, tmp_elf_file],
|
||||
args.output, args.ldflags)
|
||||
finally:
|
||||
if not args.keeptmp:
|
||||
if objinputistemp: os.remove(objinput)
|
||||
os.remove(tmp_asm_file)
|
||||
os.remove(tmp_elf_file)
|
||||
|
||||
if __name__ == '__main__':
|
||||
rv = main()
|
||||
if rv is None: pass
|
||||
else:
|
||||
try: sys.exit(int(rv))
|
||||
except: sys.exit(1)
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
import os.path, struct, sys
|
||||
import argparse, glob, shutil, subprocess
|
||||
|
||||
import hackyelf, linkmap
|
||||
from smolshared import *
|
||||
from smolparse import *
|
||||
import smol.hackyelf as hackyelf
|
||||
import smol.linkmap as linkmap
|
||||
from smol.shared import *
|
||||
from smol.parse import *
|
||||
|
||||
def readbyte(blob, off): return struct.unpack('<B', blob[off:off+1])[0], (off+1)
|
||||
def readint(blob, off): return struct.unpack('<I', blob[off:off+4])[0], (off+4)
|
||||
|
@ -55,7 +56,7 @@ def addr2off(elf, addr):
|
|||
assert aoff < x.filesz, ".bss address!"
|
||||
return aoff + x.off
|
||||
|
||||
assert False, "Address %08x not in the static address range!" % addr
|
||||
error("E: Address %08x not in the static address range!" % addr)
|
||||
|
||||
def get_needed_libs(elf, blob):
|
||||
assert elf.dyn is not None, "No DYNAMIC table present in the ELF file!"
|
||||
|
@ -100,7 +101,7 @@ def get_hashtbl(elf, blob, args):
|
|||
htaddr = struct.unpack('<I', blob[txtoff:txtoff+4])[0]
|
||||
|
||||
assert htaddr is not None, "wtf? (no hashtable address)"
|
||||
print("Hash table address: 0x%08x" % htaddr)
|
||||
#print("Hash table address: 0x%08x" % htaddr)
|
||||
htoff = addr2off(elf, htaddr)
|
||||
|
||||
tbl = []
|
|
@ -5,6 +5,7 @@ import sys
|
|||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser()
|
||||
# TODO: output null -> in-place!
|
||||
parser.add_argument('input', type=argparse.FileType('rb'), \
|
||||
help="input file to truncate")
|
||||
parser.add_argument('output', type=argparse.FileType('wb'), \
|
86
src/smol.py
86
src/smol.py
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import itertools
|
||||
import os.path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from smolshared import *
|
||||
from smolparse import *
|
||||
from smolemit import *
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-m', '--target', default='', \
|
||||
help='architecture to generate asm code for (default: auto)')
|
||||
parser.add_argument('-l', '--library', metavar='LIB', action='append', \
|
||||
help='libraries to link against')
|
||||
parser.add_argument('-L', '--libdir', metavar='DIR', action='append', \
|
||||
help="directories to search libraries in")
|
||||
|
||||
parser.add_argument('--nasm', default=shutil.which('nasm'), \
|
||||
help="which nasm binary to use")
|
||||
parser.add_argument('--cc', default=shutil.which('cc'), \
|
||||
help="which cc binary to use")
|
||||
parser.add_argument('--scanelf', default=shutil.which('scanelf'), \
|
||||
help="which scanelf binary to use")
|
||||
parser.add_argument('--readelf', default=shutil.which('readelf'), \
|
||||
help="which readelf binary to use")
|
||||
|
||||
parser.add_argument('-s', '--hash16', default=False, action='store_true', \
|
||||
help="Use 16-bit (BSD) hashes instead of 32-bit djb2 hashes. "\
|
||||
+"Must be used with -DUSE_DNLOAD_LOADER")
|
||||
parser.add_argument('-n', '--nx', default=False, action='store_true', \
|
||||
help="Use NX (i.e. don't use RWE pages). Costs the size of one phdr, "\
|
||||
+"plus some extra bytes on i386.")
|
||||
parser.add_argument('-d', '--det', default=False, action='store_true', \
|
||||
help="Make the order of imports deterministic (default: just use on "+\
|
||||
"whatever binutils throws at us)")
|
||||
|
||||
parser.add_argument('input', nargs='+', help="input object file")
|
||||
parser.add_argument('output', type=argparse.FileType('w'), \
|
||||
help="output nasm file", default=sys.stdout)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
for x in ['nasm','cc','scanelf','readelf']:
|
||||
val = args.__dict__[x]
|
||||
if val is None or not os.path.isfile(val):
|
||||
eprintf("'" + x + "' binary" + (" " if val is None
|
||||
else " ('" + val + "')") + " not found")
|
||||
sys.exit(1)
|
||||
|
||||
if args.libdir is None: args.libdir = []
|
||||
arch = args.target.tolower() if len(args.target)!=0 \
|
||||
else decide_arch(args.input)
|
||||
if arch not in archmagic:
|
||||
eprintf("Unknown architecture '" + str(arch) + "'")
|
||||
sys.exit(1)
|
||||
|
||||
syms = get_needed_syms(args.readelf, args.input)
|
||||
|
||||
paths = get_cc_paths(args.cc)
|
||||
|
||||
spaths = args.libdir + paths['libraries']
|
||||
libraries=paths['libraries']
|
||||
libnames = args.library
|
||||
libs = list(find_libs(spaths, libnames))
|
||||
symbols = {}
|
||||
#print("libs = " + str(libs))
|
||||
|
||||
for symbol, reloc in syms:
|
||||
library = find_symbol(args.scanelf, libs, libnames, symbol)
|
||||
if not library:
|
||||
eprintf("could not find symbol: {}".format(symbol))
|
||||
sys.exit(1)
|
||||
symbols.setdefault(library, [])
|
||||
symbols[library].append((symbol, reloc))
|
||||
|
||||
output(arch, symbols, args.nx, args.hash16, args.output, args.det)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
Loading…
Reference in New Issue