Compiler Writer's Guide to Linux
================================
Stephen Pelc
mailto:stephen@mpeforth.com
Copyright (c) 2005
MicroProcessor Engineering
133 Hill Lane
Southampton SO15 5AF
England
tel: +44 (0)23 8063 1441
fax: +44 (0)23 8033 9691
net: mpe@mpeforth.com
tech-support@mpeforth.com
web: www.mpeforth.com
From North America, our telephone and fax numbers are:
011 44 23 8063 1441
011 44 23 8033 9691
Acknowledgements
----------------
Anton Ertl
Andrew Haley
Albert van der Horst
Brian Raiter
Mark Washburn
Hants Linux Users Group
Introduction
============
This book was written out of severe frustration when porting
one of our products from Windows to Linux. It just doesn't have
to be that hard!
The product we ported is the VFX Forth compiler and interpreter.
Forth is an interactive and extensible language and our
implementation generates fast native code. In doing the port we
had to learn about Linux system calls, file formats, interfacing
to shared libraries, exception handling mechanisms and so on. We
kept notes as we went along.
Everything you want to know about Linux is available. Knowing where
to find it is another matter. Specifications, explanations, and
examples are scattered across the Internet. Do not attempt a Linux
port without a good cheap Internet connection. You will be using
it heavily. At the beginning I spent several hours a day surfing.
The available documents often only make good sense once you already
understand them, and some Linux programmers have a kind of
"hair shirt" attitude - "Read the source, Luke" is a common comment.
The assumption in most Linux tutorials is that you will use gcc
to produce everything.
This book is for those who want to look below that level.
Nearly all the examples in this book are in IA32 assembler
(i386 and up). We don't actually say anything about compiling
code (that's your business), but we do try to to explain what
you need to know about producing an executable file without
using gcc. This may sound masochistic to C programmers, but
it does avoid some problems later on in the development of
interactive and extensible languages.
C is the Linux lingua franca. You have to be able to read and
write some C to get anywhere with Linux development. You don't
have to like it, you just have to be able to do it. Do not expect
the C source code you read to be well documented - many of the
programmers who wrote it are Linux gurus, and they are working
at a level well beyond those of us are who are new to Linux.
That having been said, much of the Linux code I have read has
a comment level well below what I and my clients require of our
own programmers. Design information is sadly lacking but if you
ask your questions in the right places, you will be helped. I
particularly want to thank the Hampshire Linux Users Group
who tolerated my ineptitude for several months.
Setting up your Linux box
-------------------------
The days when a Linux was hard to install are now (2005) long
gone for most PCs. Our Suse Linux v9.2 installed flawlessly on
a cheap generic PC.
That was the good news. The next stage was to talk to our humble
peer-to-peer Windows network. For these we needed to install
Samba. What a nightmare! In the end we hired somebody to do
this for us, which he did in a couple of hours. We just did not
have the time to learn Samba ourselves, and learning Samba was
not the object of the exercise. There are books about Samba, buy
one or contact an existing Linux expert user for help if you are
going to install it yourself. Make sure that you can access the
Linux /usr directory from a Windows PC. Until you have
something running to the point where it can recompile
itself, you are going to be reading C header files for longer
than you can imagine.
Like most Unix programmers, Linux gurus use editors that seem
arcane and very old fashioned to Windows programmers. The main
of these are Vi and Emacs. These are incredibly powerful. Make
a decision early on as to whether you want another learning curve.
Modern Linux implementations come with editors that are almost
immediately usable by Windows programmers. Since we chose a KDE
GUI for our Linux box, we ended up using the Kate editor. When
you convert source code from Windows to Linux and back, you will
quickly discover that where Windows uses CR/LF pairs as line
terminators, Linux (and all Unix versions) uses LF (ASCII code
0x0a). The tools for converting text files are usually:
dos2unix unix2dos mac2unix
You can get to the documentation on a program using man or info:
man is simpler, info has a tutorial. For example:
man signal
Man pages often include references to other items. The signal
example refers to signal(2) and signal(7). To see these separate
entries use:
man 2 signal
man 7 signal
We did our experiments using the Nasm assembler from:
http://nasm.sourceforge.net
There are separate versions for Windows and for Linux. They seem
to be compatible and do not care about LF or CR/LF line endings.
A critical discovery, which made us believe that the whole
project was possible for Linux "newbies" like ourselves, was
Brian Raiter's excellent tutorial, "A Whirlwind Tutorial on
Creating Really Teensy ELF Executables for Linux" or, "Size
Is Everything". It can be found at:
http://www.muppetlabs.com/~breadbox/
System calls
============
EAX = call number
EBX = arg1 (leftmost in Linux docs)
ECX = arg2
EDX = arg3
ESI = arg4
EDI = arg5
EBP = arg6 (rightmost in Linux docs) EBP is only used on
kernel 2.4 onwards.
Then use:
int 0x80
The return value (if any) is in EAX.
ELF executables - the basics
============================
The ELF file format is convoluted as a result of the number of
facilities it provides. The ELF format can be used both for
unlinked object files and for linked executable files. We will
just consider what is needed to produce executable program files.
Because were are striving for simplicity and understanding, we
take advantage of being able to load our program at a fixed
address. This avoids having to worry about linking and relocation
issues.
The starting point is a set of three headers. When dealing
with ELF data structures it is common to find that data values
of zero indicate "unknown" or "none".
ELF header
----------
The ELF header identifies the file format, CPU, program entry
point and provides data about the rest of the file, including
pointers to the next two header tables.
Program header table (PHT)
--------------------------
The PHT identifies where various parts of the file are loaded into
memory, how much memory they occupy, the read/write/execute
permissions of the memory and so on. Each part is known as a
segment. Under some circumstances segments may overlap. The
ELF header and the PHT are often part of the main code segment.
Segments contain code and data that is loaded into memory. Each
segment is defined by its location and size in the file. The
segment may reserve more space in memory than is contained
in the file. The additional space will be zero filled.
Several standard types are defined, including one for defining
an interpreter. This interpreter can be a language interpreter,
but in our case, it is the shared library used to resolve
references to shared libraries used by our program.
Segments identified by the PHT are mainly involved in program
loading and execution.
Section header table (SHT)
--------------------------
The table of section header entries describes how parts of the
segment images loaded are used. Some standard section types are
defined, including those for accessing shared libraries. Sections
refer to data in loaded segments. Our minimal programs do
not require any sections, so we will come to these much later.
Sections identified by the SHT are mainly involved in linking
separately compiled object files into a single executable
program. As far as I know, the shared library loader never
references sections, so these are not required for executable
programs and by implication they are not required in shared
libraries.
Approach
--------
The examples are all written in assembler for 386 Linux. The
starting point was the set of examples by Brian Raiter in his
article on writing "really teeny Linux executables". We start
from the ground up by defining a program that just exits back
to the operating system. The assembler is used to generate a
memory image file that includes all the required headers. The
assembler used is NASM, which can be found at:
http://nasm.sourceforge.net
The first program
-----------------
This is the code assembled from the file tiny.asm. The special
labels $$ and $ refer to the start and current addresses of the
code.
BITS 32
org 0x08048000 ; conventional Linux i32 load address
ehdr: ; ELF file header
db 0x7F, "ELF", 1, 1, 1 ; e_ident
times 9 db 0
dw 2 ; e_type 2=executable, 3=shared lib
dw 3 ; e_machine 3=i32/386
dd 1 ; e_version 1=current
dd _start ; e_entry absolute entry point
dd phdr - $$ ; e_phoff file offset to PHT, 0=none
dd 0 ; e_shoff file offset to SHT, 0=none
dd 0 ; e_flags 0 for i386
dw ehdrsize ; e_ehsize ELF header size
dw phdrsize ; e_phentsize PHT entry size
dw 1 ; e_phnum #entries in PHT
dw 0 ; e_shentsize SHT entry size
dw 0 ; e_shnum #entries in SHT
dw 0 ; e_shstrndx file offset to string table, 0=none
ehdrsize equ $ - ehdr
phdr: ; Program Header Table (PHT) with one entry
dd 1 ; p_type 1=load into memory
dd 0 ; p_offset file offset to start of segment
dd $$ ; p_vaddr virt. address where loaded
dd $$ ; p_paddr abs. address where loaded
dd filesize ; p_filesz how much loaded from file
dd filesize ; p_memsz segment size in meory
dd 5 ; p_flags permissions, R-X
dd 0x1000 ; p_align alignment required
phdrsize equ $ - phdr
_start: ; entry point
mov bl, 42 ; return code in bottom 8 bits of EAX
mov eax, 1 ; exit function call number
int 0x80 ; Linux i32 system call
filesize equ $ - $$
Assemble it, make it executable and run it, displaying the return
code.
$ nasm -f bin -o tiny.elf tiny.asm
$ chmod +x tiny.elf
$ ./tiny.elf ; echo $?
42
Now we've shown (courtesy of Brian Raiter) that you can produce
a Linux ELF binary without using the standard gcc tools. In the
next section we will extend it to deal with shared libraries.
Why call shared libraries at all?
---------------------------------
There are two reasons:
1) to take advantage of existing code. For many of these
libraries, the source code is available if you want it.
2) to access the standard C libraries. If you want your
language to be easy to use, your system has to match
widely-used documentation. In practice this means the
standard C library libc.so and friends. In some cases
the library function may just be a wrapper around the
function call itself, but in many cases there is an
additional level of functionality. This is especially
true of the file system interface, which contains
buffered operations and (except for STDIN=0, STDOUT=1
and STDERR=2), can use incompatible file handles.
Calling a function in a shared library
--------------------------------------
The next stage is to call a function in a shared library. At this
point all the apparent simplicity of the first example disappears.
It's not that that anything is particularly difficult, it's just
that there is a great deal of it.
In order to access shared libraries, we need to use the following
functions:
dlopen dlsym dlclose dlerror
These functions are in the shared library libdl.so, so we cannot
access these functions until we have loaded the library. A
consequence of this is that all applications must provide enough
information in the ELF file to access at least these functions.
In order to use the shared library facilities of an ELF file,
we have to add two new segments to the file, of type PT_INTERP
and PT_DYNAMIC. The PT_INTERP segment specifies a program
interpreter, usually ld-linux.so (itself a shared library) which
knows how to process an ELF file. The PT_DYNAMIC segment holds
data required to get the libdl.so library into our program's
memory space and then to find the real address of the functions.
The PT_DYNAMIC section usually starts with the _DYNAMIC structure,
a block of type/value pairs which define the type and location of
various other structures. Constructing the structure is dicussed
later.
Before we can actually wite out these two segments we must
understand how the various pieces of data are used.
Calling the functions
---------------------
The addresses of external functions and data are fixed up by the
shared library loader (defined by the PT_INTERP segment) in a
table called the Global Offset Table (GOT). External functions
are called through the Program Linkage Table (PLT). You do not
call indirectly through the GOT because entries in the GOT may
only be resolved the first time the function is called. This is
important if you are writing an interpreter (such as Forth, Lisp, Perl
etc) which may contain external function references that are not
required. Resolving all your external references at program load
time gives the most predictable performance at the expense of
start-up time. Resolving them when they are first called prevents
unused libraries ever being loaded and so reduces memory requirements.
The Global Offset Table is an array of 32 bit words. The first
three entries are special:
index 0: points to the _DYNAMIC structure for i386 systems
index 1: dynamic linker specific (we set 0 here)
index 2: address of linker function (we set 0 here)
index 3: address of function1() - set by us to point to PLT1+6 below
index 4: address of function2() - set by us to point to PLT2+6 below
...
The Program Linkage Table is made up of 16 byte units of code,
the first of which is special, accessing the dynamic linker itself.
PLT0: ; the magic bit
push dword [GOT+4] ; contents of GOT[1] - 6 bytes
jump [GOT+8] ; use dest at GOT[2] - 6 bytes
nop ; padding to 16 bytes
nop
nop
nop
PLT1: ; access to funtion1()
jump [GOT+12] ; via GOT[3] which points to next instruction - 6 bytes
push offRelocTab1 ; offset in relocation table for function1() - 5 bytes
jmp PLT0 ; go to dynamic linker - 5 bytes
PLT1: ; access to funtion2()
jump [GOT+16] ; via GOT[4] which points to next instruction - 6 bytes
push offRelocTab1 ; offset in relocation table for function1() - 5 bytes
jmp PLT0 ; go to dynamic linker - 5 bytes
PLT2:
...
The ELF file entry in GOT[0] permits the dyamic linker to find the
other file data it needs when the dynamic linker is initialised. It
then fills in GOT[1] and GOT[2] so that the executable can use the
dynamic linker. Assuming that functions are resolved on the first
call (lazy binding), and because we set the GOT entry to point to the
following push instruction, the jump instructions at PLTx act as nops.
Then the offset in the relocation table for the function we want is pushed,
and we branch to the code at PLT0 which accesses the dynamic linker.
This in turn replaces the relevant GOT entry with the absolute address
of the function and executes the function. The next time we execute
the same function, the GOT entry is now correct and the function executes
directly. As far as our executable is concerned, we can just
call the relevant PLT entry point.
Before we can actually make the call, we have to provide one relocation
table entry for each function we want to call, otherwise the dynamic
linker cannot find and resolve the functions we want to use.
Symbol table, relocation and hashing
------------------------------------
Functions and data in a shared library are identified by name and
type. The dynamic linker needs to know which shared library to use
and what to look for. What to look for is handled by symbol tables.
Where to look is handled by the order of library entries in the
_DYNAMIC structure. The relocation table tells the dynamic linker
which symbol entries to use, what to do with them (relocation type)
and where to put the result (function address)
The address of the relocation table (a section usually called .plt.rel)
is an entry in the _DYNAMIC structure. Each entry in this particular
relocation table consists of:
dd GOT[x] ; address of xth entry in GOT table
dd SymIndexY<<8 + 7 ; symbol table entry and relocation
The first entry is the absolute address of the relevant GOT entry.
The second entry is the symbol table index shifted left 8 bits plus
the relocation type, which is CPU specific and 7 for i386. This type
is called R_386_JMP_SLOT in the ELF literature. Now we need to know
how to make the symbol table entries.
Each entry in a symbol table
has the following form, where the first entry in the table is all
zeros. The symbol table is accessed by index of entry number,
where the zero entry is a dummy so that index 0 can represent
unknown or the end of a list of indices. Useful symbol table
entries start at one. For the four functions we need (dlopen,
dlsym, dlclose and dlerror), we will need a symbol table with five
entries incuding the dummy entry.
Names are referenced by an index (byte offset) into a "String Table",
which is just a set of zero terminated strings. The address of the
string table used by the symbol table is defined later.
StringTable:
db 0 ; first entry always 0
idxdlopen equ $-StringTable
db "dlopen",0
idxdlsym equ $-StringTable
db "dlsym",0
...
SymbolTable:
; Dummy entry - entry 0
dd 0 ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
; first real entry - entry 1
dd idxdlopen ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0x12 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
...
According to several correspondents, the st_size field should
contain the size of the function, but the size is not required for
program loading. This field can be used by debuggers or for version
checking. I have been told that the GNU linker may assume that all
required libraries are present at link time.
If only it was that simple! ELF files need a hash table to
improve search speed. This is important for large applications
that may require tens of thousands of name searches. First of
all we will look at how to do it properly. Then we will
use that knowledge to describe a cheat that is suitable when
we only have a small number of searches to perform.
Search performance is made particularly bad because the kernel
uses zero terminated strings, and so checking the length of a
string involves reading every character. By the time a C++
compiler has finished mangling names or an Ada compiler has
added package names, names can be very long. Symbol names
longer than 1000 characters have been observed in applications
such as OpenOffice. See "How to Write Shared Libraries" by Ulrich
Drepper for more of the gory details. It may be found at:
people.redhat.com/drepper/dsohowto.pdf
Hashing properly
................
ELF dynamic linkers look up names using
a hash table supplied in the executable. We have to construct it
ourselves. The hash function follows.
unsigned long
elf_hash(const unsigned char *name)
{
unsigned long h = 0, g;
while (*name) {
h = (h << 4) + *name++;
if (g = h & 0xf0000000)
h ^= g >> 24;
h &= ~g;
}
return h;
}
For each symbol name, you have to calculate the hash value.
The hash table consists of two tables each containing symbol table
entry numbers. The first is called the bucket table, which contains
nb entries. You choose nb yourself. For a small number of symbols,
this might be the number of symbols. When a name is being looked up,
the hash value of the name modulo nb (call it y) is used as a
zero-based index into the bucket table. The symbol table index
in that entry is then checked to see if the name matches. If
the name does not match, we have to use the chain table.
The failed symbol table index is used to pick the next symbol
table index for a name with the same value of y. If that index
fails the process repeats. Since we have four functions to
search for, we will use a four entry bucket table, and a five
entry chain table (index zero is the dummy symbol).
The symbol table entries (chosen by us) and hash values for
each function (modulo 4) are as follows.
function SymIdx y: Hash modulo 4
dlopen 1 2
dlsym 2 1
dlclose 3 1
dlerror 4 2
You will note from this that the hash function is not very good.
You have to go up to modulo 10 to get different y values for
each of these functions.
hashtable:
dd 4 ; number of buckets = 0..3 (modulo 4)
dd 5 ; number of chains = 0..4 (dummy plus four symbols)
buckets: ; y=0..3
dd 0 ; y=0, nothing
dd 2 ; y=1, first match is SymIdx=2, dlsym
dd 1 ; y=2, first match is SymIdx=1, dlopen
dd 0 ; nothing for y=3
chains:
dd 0 ; 0=dummy
dd 4 ; 1=dlopen, not dlopen, try dlerror
dd 3 ; 2=dlsym, not dlsym, try dlclose
dd 0 ; 3=dlclose, last in chain for y=1
dd 0 ; 4=dlerror, last in chain for y=2
Hashing by cheating
...................
If we only use 1 bucket, we can rely on x modulo 1 always being
zero. We can make the single bucket entry be for for symbol index
one. Then, in the chain, entry one points to entry two and so on.
Our revised table can probably be generated by a simple function
or macro.
hashtable:
dd 1 ; number of buckets, modulo 1
dd 5 ; number of chains = 0..4 (dummy plus four symbols)
buckets:
dd 1 ; y=0, try SymIdx=1
chains:
dd 0 ; 0=dummy
dd 2 ; next
dd 3 ; next
dd 4 ; next
dd 0 ; next=end of list
The dynamic segment
-------------------
The dynamic linker has to be able to find all the data we have
created. The PT_DYNAMIC segment provides this information. Like
the PT_INTERP segment which specifies the dynamic linker to use,
the PT_DYNAMIC segment can be part of the main code segment.
You can treat the PT_INTERP and PT_DYNAMIC segments as
"pseudo-segments" that can be part of other segments.
The required data in the PT_DYNAMIC segment is a table of type/data
pairs, terminated by a pair of zeros.
_DYNAMIC:
dd 4, HashTable ; DT_HASH, address of hash table
dd 5, StringTable ; DT_STRTAB, address of string table
dd 10, StringTableSize ; DT_STRSZ, string table size in bytes
dd 6, SymbolTable ; DT_SYMTAB, address of symbol table
dd 11, 16 ; DT_SYMENT, symbol table entry size in bytes
dd 3, GOT ; DT_PLTGOT, base address of Global Offset Table
dd 23, RelocTable ; DT_JMPREL, base address of relocation table
dd 2, RelocTableSize ; DT_PLTRELSZ, size of relocation table in bytes
dd 20, 17 ; DT_PLTREL, DT_REL, indicate type of PLT relocation
dd 1, idxlibdl ; DT_NEEDED, string table index ; library name
; dd 21, 0 ; DT_DEBUG, none, no debug information
dd 0, 0 ; DT_NULL, ignored - end of list
The second program
------------------
Now we have to tie everything together in one program. The source
code, executables and scripts are in examples/openlib on the CD
with this book. The source code is in openlib.asm and the script
to build it is in openlib.sh. As before we will use Nasm to
assemble it.
Some of the apparently peculiar layout is a consequence of my
objective to write an interpreter. I wanted to keep as much as
possible in the same place in order to minimise the workload
when a new executable saves itself.
A number of ALIGN directives are used to force code to memory
boundaries. Some are required by the following code, but many
are used to make the object file easier to use when using the
Linux ELF file tools such as readelf and objdump.
We can simplify with some extra cheating. The principal cheat
is to mark the main text segment as writable. This will not work
for secure applications, but works for day to day operation.
; openlib.asm - builds an ELF file that calls libraries
; - no linker required
; Copyright (c) 2005
; MicroProcessor Engineering
; 133 Hill Lane
; Southampton SO15 5AF
; England
;
; tel: +44 (0)23 8063 1441
; fax: +44 (0)23 8033 9691
; net: mpe@mpeforth.com
; tech-support@mpeforth.com
; web: www.mpeforth.com
;
; From North America, our telephone and fax numbers are:
; 011 44 23 8063 1441
; 011 44 23 8033 9691
;
;
; To do
; =====
;
; Change history
; ==============
;
; ********
; Prologue
; ********
BITS 32
org 0x08048000 ; conventional Linux i32 load address
TextSize equ 0x00800000 ; ask for 8 megabytes for my interpreter
; **********
; ELF header
; **********
ehdr: ; ELF file header
db 0x7F, "ELF", 1, 1, 1 ; e_ident
times 9 db 0
dw 2 ; e_type 2=executable, 3=shared lib
dw 3 ; e_machine 3=i32/386
dd 1 ; e_version 1=current
dd _start ; e_entry absolute entry point
dd PHT-$$ ; e_phoff file offset to PHT, 0=none
dd 0 ; e_shoff file offset to SHT, 0=none
dd 0 ; e_flags 0 for i386
dw ehdrsize ; e_ehsize ELF header size
dw phdrsize ; e_phentsize PHT entry size
dw 3 ; e_phnum #entries in PHT
dw 0 ; e_shentsize SHT entry size
dw 0 ; e_shnum #entries in SHT
dw 0 ; e_shstrndx file offset to string table, 0=none
ehdrsize equ $-ehdr
; ***************
; Program headers
; ***************
align 64 ; for dumps
InterpName: ; name of the dynamic linker
db "/lib/ld-linux.so.2",0
InterpNameSize equ $-InterpName
align 64 ; for dumps
align 4 ; finally
PHT: ; Program Header Table (PHT) with three entries
; it is legal and useful to include the headers in memory
; that is visible to the program.
; Program interpreter for shared library access
PHinterp:
dd 3 ; p_type 3=PT_INTERP
dd InterpName-$$ ; p_offset offset into file
dd InterpName ; p_vaddr (virtual) run-time address
dd InterpName ; p_paddr (physical) run-time address
dd InterpNameSize ; p_filesz #bytes to load from file
dd InterpNameSize ; p_memsz #bytes to occupy in memory
dd 4 ; p_flags permissions R--, 4=read,2=write,1=exec
dd 0x1 ; p_align page size
phdrsize equ $-PHinterp
; Main code (.text) and fixed data
PHtext:
dd 1 ; p_type 1=load into memory
dd 0 ; p_offset file offset to start of segment
dd $$ ; p_vaddr virt. address where loaded
dd $$ ; p_paddr abs. address where loaded
dd filesize ; p_filesz how much loaded from file
dd filesize ; p_memsz segment size in meory
dd 7 ; p_flags permissions RWX, 4=read,2=write,1=exec
dd 0x1000 ; p_align alignment required
; Dynamic info section for shared libraries, PT_DTNAMIC segment
PHdynamic:
dd 2 ; p_type 1=load mem, 2=dynlink info
dd DynOffset ; p_offset offset into file
dd DynAddr ; p_vaddr (virtual) run-time address
dd DynAddr ; p_paddr (physical) run-time address (irrelevant here)
dd DynSize ; p_filesz #bytes to load from file
dd DynSize ; p_memsz #bytes to occupy in memory
dd 6 ; p_flags permissions RW-, 4=read,2=write,1=exec
dd 0x04 ; p_align page size
_start:
jmp realstart
signon:
db "CallLib.elf example",0x0a,0
l_signon equ $-1-signon
; parameter strings and numbers
libname:
db "libc.so.6",0
printfname:
db "printf",0
formatstring:
db "Hello from %s, my number is %d",0x0a,0
myname:
db "Stephen",0
mynum:
dd 12345678
; storage
plib: ; holds library handle
dd -1
pprintf: ; holds address of printf
dd -1
; Extracts from the C header files
; void *dlopen(const char *filename, int flag);
; void *dlsym(void *handle, const char*symbol);
; int dlclose(void *handle);
; char *dlerror(void);
; #define RTLD_LAZY 0x00001 /* Lazy function call binding. */
; #define RTLD_NOW 0x00002 /* Immediate function call binding. */
; #define RTLD_BINDING_MASK 0x3 /* Mask of binding time value. */
; #define RTLD_NOLOAD 0x00004 /* Do not load the object. */
; #define RTLD_GLOBAL 0x00100
realstart:
; sign on
mov ecx, signon
mov edx, l_signon
call primtype
; Open the library
push 1 ; RTLD_LAZY
push libname ; "libc.so.6"
call dlopen ; open library
mov [plib], eax ; EAX is library handle or error code
add esp, 8 ; remove the two items passed to dlopen
; mov ebx, eax ; return value in EAX
; call showhex ; display result
mov ebx, 1 ; return code
cmp eax, 0 ; test return value
je getout
; Find printf
push printfname ; string "printf"
push dword [plib] ; library handle
call dlsym ; find function address
mov [pprintf], eax ; EAX = function address or error code
add esp, 8 ; remove two paramters
; mov ebx, eax
; call showhex ; display result
mov ebx, 2 ; return code
cmp eax, 0 ; test return value
je getout
; call printf
push dword [mynum] ; second paramter to format string
push myname ; first parameter to format string
push formatstring ; format string itself
call [pprintf] ; print and expand format string
add esp, 12 ; remove three arguments
; mov ebx, eax ; number of characters displayed
; call showhex ; display result
; close library
push dword [plib] ; library handle
call dlclose
add esp, 4 ; remove one argument
; mov ebx, eax
; call showhex ; display result
mov ebx, 3 ; error return code
cmp eax, 0 ; test result
jne getout
; back to Linux
mov ebx, 42 ; success return code
getout:
mov eax, 1 ; exit function number
int 0x80 ; Linux system call
; simple type routine, ECX=caddr EDX=len
primtype:
mov eax, 4 ; write call
mov ebx, 1 ; STDOUT=1
int 0x80
ret
; emit single character, EAX=char
primemit:
push eax
mov ecx, esp
mov edx, 1
call primtype
pop eax
ret
; Display EBX as 8 digit hex number
showhex:
push eax
push ebx
mov ecx, 8
sh1:
push ecx
push ebx
mov eax, ebx
shr eax, 28
cmp eax, 9
jle sh2
add eax, 7
sh2:
add eax, 0x30
call primemit
pop ebx
pop ecx
shl ebx, 4
sub ecx, 1
jne sh1
mov eax, 0x20
call primemit
pop ebx
pop eax
ret
; ************
; String Table
; ************
align 64 ; for dumps
align 4 ; finally
StringTable:
db 0 ; first entry always 0
idxlibdl equ $-StringTable
db "libdl.so.2",0
idxdlopen equ $-StringTable
db "dlopen",0
idxdlsym equ $-StringTable
db "dlsym",0
idxdlclose equ $-StringTable
db "dlclose",0
idxdlerror equ $-StringTable
db "dlerror",0
StringTableSize equ $-StringTable
; ************
; Symbol Table
; ************
align 64 ; for dumps
align 4 ; finally
SymbolTable:
; Dummy entry - entry 0
dd 0 ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
; entry 1
dd idxdlopen ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0x12 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
; entry 2
dd idxdlsym ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0x12 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
; entry 3
dd idxdlclose ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0x12 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
; entry 4
dd idxdlerror ; st_name index into a string table
dd 0 ; st_value 0 for external symbols
dd 0 ; st_size 0=unknown
db 0x12 ; st_info binding=1(global), type: 1=data 2=function
db 0 ; st_other
dw 0 ; st_shndx 0 indicates external symbol
; **********
; Hash Table
; **********
HashTable:
dd 1 ; number of buckets, modulo 1
dd 5 ; number of chains = 0..4 (dummy plus four symbols)
buckets:
dd 1 ; y=0, try SymIdx=1
chains:
dd 0 ; 0=dummy
dd 2 ; next
dd 3 ; next
dd 4 ; next
dd 0 ; next=end of list
; *******************
; Global Offset Table
; *******************
align 64 ; for dumps
align 4 ; finally
GOT:
dd _DYNAMIC ; points to start of _DYNAMIC structure
dd 0 ; fixed up by dynamic linker
dd 0 ; fixed up by dynamic linker
dd dlopen+6 ; GOT+12
dd dlsym+6 ; GOT+16
dd dlclose+6 ; GOT+20
dd dlerror+6 ; GOT+24
; *********************
; Program Linkage Table
; *********************
align 64 ; for dumps
align 16 ; finally
PLT0:
push dword [GOT+4] ; contents supplied by dynamic linker
jmp [GOT+8] ; entry to dynamic linker
align 16
dlopen:
jmp [GOT+12] ; through GOT[3], initially points to next ins.
push 0x00 ; offset in relocation table blow
jmp PLT0
align 16
dlsym:
jmp [GOT+16] ; through GOT[4], initially points to next ins.
push 0x08 ; offset in relocation table blow
jmp PLT0
align 16
dlclose:
jmp [GOT+20] ; through GOT[5], initially points to next ins.
push 0x10 ; offset in relocation table blow
jmp PLT0
align 16
dlerror:
jmp [GOT+24] ; through GOT[3], initially points to next ins.
push 0x18 ; offset in relocation table blow
jmp PLT0
align 16
PLTx:
; ****************
; Relocation Table
; ****************
align 64 ; for dumps
align 4 ; finally
RelocTable:
; for dlopen
dd GOT+12 ; first entry is GOT[3], GOT[0..2] reserved
dd 0x0107 ; Symbol Index = 1<<8 + R_386_JMP_SLOT
; for dlsym
dd GOT+16 ; second entry is GOT[4]
dd 0x0207 ; Symbol Index = 2<<8 + R_386_JMP_SLOT
; for dlclose
dd GOT+20 ; third entry is GOT[5]
dd 0x0307 ; Symbol Index = 3<<8 + R_386_JMP_SLOT
; for dlerror
dd GOT+24 ; fourth entry is GOT[6]
dd 0x0407 ; Symbol Index =4<<8 + R_386_JMP_SLOT
RelocTableSize equ $-RelocTable
; ***************
; Dynamic segment
; ***************
align 256
DynAddr: ; base address of segment
DynOffset equ DynAddr-$$ ; offset of segment in file
_DYNAMIC:
dd 4, HashTable ; DT_HASH, address of hash table
dd 5, StringTable ; DT_STRTAB, address of string table
dd 10, StringTableSize ; DT_STRSZ, string table size in bytes
dd 6, SymbolTable ; DT_SYMTAB, address of symbol table
dd 11, 16 ; DT_SYMENT, symbol table entry size in bytes
dd 3, GOT ; DT_PLTGOT, base address of Global Offset Table
dd 23, RelocTable ; DT_JMPREL, base address of relocation table
dd 2, RelocTableSize ; DT_PLTRELSZ, size of relocation table in bytes
dd 20, 17 ; DT_PLTREL, DT_REL, indicate type of PLT relocation
dd 1, idxlibdl ; DT_NEEDED, string table index ; library name
; dd 21, 0 ; DT_DEBUG, none, no debug information
dd 0, 0 ; DT_NULL, ignored - end of list
align 256
DynSize equ $-DynAddr
; **********************************
; end of everything - we're all done
; **********************************
filesize equ $-$$ ; total file size
Exceptions and signals
======================
I am grateful to Bob Dunlop and Andrew Haley for pointing me in
right directions for information about signal handling.
Bob Dunlop:
>
> 1) Which stack is a signal handler called on?
> A kernel stack
> the faulting application's stack
> another stack
> Yes, I could define a sigaltstack, but ...
The faulting applications stack unless you've set an alternate via
sigaltstack().
> 2) Must I do a sigreturn?
NO!!!
The kernel signal routines cook up a special stack frame so that
when you perform a standard return from your handler the sigreturn()
function is called automagically. Never try to perform an explicit
sigreturn().
> 3) How do I find the faulting CPU context (register set) so that
> I can display information in my form and modify return
You are now into the relms of horribly non-portable code. Not only
between CPU versions but also kernel versions.
See "asm/sigcontext.h" for definitions of the register storage.
You'll have to decode /usr/src/linux/arch/i386/kernel/signal.c for
how to find it on the stack. Seem to remember that the third
argument to the signal handler specified as void * is your friend
but the last time I did this was on a 2.2.x kernel.
Andrew Haley:
> The next topic is signals/exceptions. In order to protect our
> interpreter, I need to catch errors such as divide by zero.
> However, in the sigxxxx call documentation, I have not yet found
> out:
>
> 1) Which stack is a signal handler called on?
> A kernel stack
> the faulting application's stack
> another stack
> Yes, I could define a sigaltstack, but ...
It depends on whether a signal is synchronous or not. The signals
you're interested in, such as SEGV, are synchronous, so they are
delivered immediately to the thread that caused them.
> 2) Must I do a sigreturn?
No. In fact, you may *not* legally return from a SEGV or FPE, 'cos
the PC is still pointing at the failing instruction and you'll only
trigger it again.
> 3) How do I find the faulting CPU context (register set) so that
> I can display information in my form and modify return
> addresses. Eventually, I want to provide what Windows people
> call an "in-process debugger".
See gcc/libjava/include/i386-signal.h for a (fairly evil) worked
example of using the signal context to get the PC. Also, read and
understand the comments in that file, particularly the one about the
struct passed to the sigcontext.
---------------------------------------------------------
Date sent: Tue, 30 Aug 2005 10:23:17 -0400
To: "Stephen Pelc"
Subject: Re: Using C Libraries From Assembly
Copies to: linux-assembly@vger.kernel.org
From: "Richard Cooper"
> The next task is to set up signal handlers from assembly.
That's not too difficult. From your guide, it looks like you've got it
mostly figured out already.
%idefine sys_signal 48
%idefine SIG_SEGV 11
mov eax, sys_signal
mov ebx, SIG_SEGV
mov ecx, signal_handler
int 0x80
signal_handler
printf "We crashed!!!\n";
ret
Well, actually, you wouldn't want to return with SIG_SEGV, but...as long
as you don't re-register the handler, I imagine returning would terminate
your program.
One thing you have to keep in mind that I didn't see in your guide is that
you have to re-register the signal hander every time the signal gets
called. Also, if your program recieves a signal that it doesn't have a
handler set up for it will be terminated "kill -9" style.
The real trick is figuring out the register contents at the time of the
crash.
pushfd; pop dword [r.flags]; cld
mov eax, [esp + 13 * 4]; mov [r.eax], eax
mov eax, [esp + 10 * 4]; mov [r.ebx], eax
mov eax, [esp + 12 * 4]; mov [r.ecx], eax
mov eax, [esp + 11 * 4]; mov [r.edx], eax
mov eax, [esp + 7 * 4]; mov [r.esi], eax
mov eax, [esp + 6 * 4]; mov [r.edi], eax
mov eax, [esp + 8 * 4]; mov [r.ebp], eax
mov eax, [esp + 9 * 4]; mov [r.esp], eax
mov eax, [esp + 16 * 4]; mov [r.eip], eax
Since I'm using pushfd, I guess I didn't find the flags saved anywhere.
Took me a while to figure that out. There's another set of registers on
the stack in front of that set, but they aren't the right ones, and where
they come from, I have no idea. I also have no idea if that works on any
kernel other than my own.
I'll have to have a look at that third argument to the signal handler some
time, if it points to where I'm getting the registers from now, then I'll
go ahead and use it.
Data structures and #defines
----------------------------
from asm/signal.h
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
from: asm/sigcontext.h
struct sigcontext {
unsigned short gs, __gsh;
unsigned short fs, __fsh;
unsigned short es, __esh;
unsigned short ds, __dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
unsigned long esp_at_signal;
unsigned short ss, __ssh;
struct _fpstate __user * fpstate;
unsigned long oldmask;
unsigned long cr2;
};
System call numbers
===================
/usr/include/asm/unistd.h
-------------------------
#ifndef _ASM_I386_UNISTD_H_
#define _ASM_I386_UNISTD_H_
/*
* This file contains the system call numbers.
*/
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_oldfstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
#define __NR_rmdir 40
#define __NR_dup 41
#define __NR_pipe 42
#define __NR_times 43
#define __NR_prof 44
#define __NR_brk 45
#define __NR_setgid 46
#define __NR_getgid 47
#define __NR_signal 48
#define __NR_geteuid 49
#define __NR_getegid 50
#define __NR_acct 51
#define __NR_umount2 52
#define __NR_lock 53
#define __NR_ioctl 54
#define __NR_fcntl 55
#define __NR_mpx 56
#define __NR_setpgid 57
#define __NR_ulimit 58
#define __NR_oldolduname 59
#define __NR_umask 60
#define __NR_chroot 61
#define __NR_ustat 62
#define __NR_dup2 63
#define __NR_getppid 64
#define __NR_getpgrp 65
#define __NR_setsid 66
#define __NR_sigaction 67
#define __NR_sgetmask 68
#define __NR_ssetmask 69
#define __NR_setreuid 70
#define __NR_setregid 71
#define __NR_sigsuspend 72
#define __NR_sigpending 73
#define __NR_sethostname 74
#define __NR_setrlimit 75
#define __NR_getrlimit 76 /* Back compatible 2Gig limited rlimit */
#define __NR_getrusage 77
#define __NR_gettimeofday 78
#define __NR_settimeofday 79
#define __NR_getgroups 80
#define __NR_setgroups 81
#define __NR_select 82
#define __NR_symlink 83
#define __NR_oldlstat 84
#define __NR_readlink 85
#define __NR_uselib 86
#define __NR_swapon 87
#define __NR_reboot 88
#define __NR_readdir 89
#define __NR_mmap 90
#define __NR_munmap 91
#define __NR_truncate 92
#define __NR_ftruncate 93
#define __NR_fchmod 94
#define __NR_fchown 95
#define __NR_getpriority 96
#define __NR_setpriority 97
#define __NR_profil 98
#define __NR_statfs 99
#define __NR_fstatfs 100
#define __NR_ioperm 101
#define __NR_socketcall 102
#define __NR_syslog 103
#define __NR_setitimer 104
#define __NR_getitimer 105
#define __NR_stat 106
#define __NR_lstat 107
#define __NR_fstat 108
#define __NR_olduname 109
#define __NR_iopl 110
#define __NR_vhangup 111
#define __NR_idle 112
#define __NR_vm86old 113
#define __NR_wait4 114
#define __NR_swapoff 115
#define __NR_sysinfo 116
#define __NR_ipc 117
#define __NR_fsync 118
#define __NR_sigreturn 119
#define __NR_clone 120
#define __NR_setdomainname 121
#define __NR_uname 122
#define __NR_modify_ldt 123
#define __NR_adjtimex 124
#define __NR_mprotect 125
#define __NR_sigprocmask 126
#define __NR_create_module 127
#define __NR_init_module 128
#define __NR_delete_module 129
#define __NR_get_kernel_syms 130
#define __NR_quotactl 131
#define __NR_getpgid 132
#define __NR_fchdir 133
#define __NR_bdflush 134
#define __NR_sysfs 135
#define __NR_personality 136
#define __NR_afs_syscall 137 /* Syscall for Andrew File System */
#define __NR_setfsuid 138
#define __NR_setfsgid 139
#define __NR__llseek 140
#define __NR_getdents 141
#define __NR__newselect 142
#define __NR_flock 143
#define __NR_msync 144
#define __NR_readv 145
#define __NR_writev 146
#define __NR_getsid 147
#define __NR_fdatasync 148
#define __NR__sysctl 149
#define __NR_mlock 150
#define __NR_munlock 151
#define __NR_mlockall 152
#define __NR_munlockall 153
#define __NR_sched_setparam 154
#define __NR_sched_getparam 155
#define __NR_sched_setscheduler 156
#define __NR_sched_getscheduler 157
#define __NR_sched_yield 158
#define __NR_sched_get_priority_max 159
#define __NR_sched_get_priority_min 160
#define __NR_sched_rr_get_interval 161
#define __NR_nanosleep 162
#define __NR_mremap 163
#define __NR_setresuid 164
#define __NR_getresuid 165
#define __NR_vm86 166
#define __NR_query_module 167
#define __NR_poll 168
#define __NR_nfsservctl 169
#define __NR_setresgid 170
#define __NR_getresgid 171
#define __NR_prctl 172
#define __NR_rt_sigreturn 173
#define __NR_rt_sigaction 174
#define __NR_rt_sigprocmask 175
#define __NR_rt_sigpending 176
#define __NR_rt_sigtimedwait 177
#define __NR_rt_sigqueueinfo 178
#define __NR_rt_sigsuspend 179
#define __NR_pread64 180
#define __NR_pwrite64 181
#define __NR_chown 182
#define __NR_getcwd 183
#define __NR_capget 184
#define __NR_capset 185
#define __NR_sigaltstack 186
#define __NR_sendfile 187
#define __NR_getpmsg 188 /* some people actually want streams */
#define __NR_putpmsg 189 /* some people actually want streams */
#define __NR_vfork 190
#define __NR_ugetrlimit 191 /* SuS compliant getrlimit */
#define __NR_mmap2 192
#define __NR_truncate64 193
#define __NR_ftruncate64 194
#define __NR_stat64 195
#define __NR_lstat64 196
#define __NR_fstat64 197
#define __NR_lchown32 198
#define __NR_getuid32 199
#define __NR_getgid32 200
#define __NR_geteuid32 201
#define __NR_getegid32 202
#define __NR_setreuid32 203
#define __NR_setregid32 204
#define __NR_getgroups32 205
#define __NR_setgroups32 206
#define __NR_fchown32 207
#define __NR_setresuid32 208
#define __NR_getresuid32 209
#define __NR_setresgid32 210
#define __NR_getresgid32 211
#define __NR_chown32 212
#define __NR_setuid32 213
#define __NR_setgid32 214
#define __NR_setfsuid32 215
#define __NR_setfsgid32 216
#define __NR_pivot_root 217
#define __NR_mincore 218
#define __NR_madvise 219
#define __NR_madvise1 219 /* delete when C lib stub is removed */
#define __NR_getdents64 220
#define __NR_fcntl64 221
/* 223 is unused */
#define __NR_gettid 224
#define __NR_readahead 225
#define __NR_setxattr 226
#define __NR_lsetxattr 227
#define __NR_fsetxattr 228
#define __NR_getxattr 229
#define __NR_lgetxattr 230
#define __NR_fgetxattr 231
#define __NR_listxattr 232
#define __NR_llistxattr 233
#define __NR_flistxattr 234
#define __NR_removexattr 235
#define __NR_lremovexattr 236
#define __NR_fremovexattr 237
#define __NR_tkill 238
#define __NR_sendfile64 239
#define __NR_futex 240
#define __NR_sched_setaffinity 241
#define __NR_sched_getaffinity 242
#define __NR_set_thread_area 243
#define __NR_get_thread_area 244
#define __NR_io_setup 245
#define __NR_io_destroy 246
#define __NR_io_getevents 247
#define __NR_io_submit 248
#define __NR_io_cancel 249
#define __NR_fadvise64 250
#define __NR_exit_group 252
#define __NR_lookup_dcookie 253
#define __NR_epoll_create 254
#define __NR_epoll_ctl 255
#define __NR_epoll_wait 256
#define __NR_remap_file_pages 257
#define __NR_set_tid_address 258
#define __NR_timer_create 259
#define __NR_timer_settime (__NR_timer_create+1)
#define __NR_timer_gettime (__NR_timer_create+2)
#define __NR_timer_getoverrun (__NR_timer_create+3)
#define __NR_timer_delete (__NR_timer_create+4)
#define __NR_clock_settime (__NR_timer_create+5)
#define __NR_clock_gettime (__NR_timer_create+6)
#define __NR_clock_getres (__NR_timer_create+7)
#define __NR_clock_nanosleep (__NR_timer_create+8)
#define __NR_statfs64 268
#define __NR_fstatfs64 269
#define __NR_tgkill 270
#define __NR_utimes 271
#define __NR_fadvise64_64 272
#define __NR_vserver 273
#define __NR_mbind 274
#define __NR_get_mempolicy 275
#define __NR_set_mempolicy 276
#define __NR_mq_open 277
#define __NR_mq_unlink (__NR_mq_open+1)
#define __NR_mq_timedsend (__NR_mq_open+2)
#define __NR_mq_timedreceive (__NR_mq_open+3)
#define __NR_mq_notify (__NR_mq_open+4)
#define __NR_mq_getsetattr (__NR_mq_open+5)
#define __NR_sys_kexec_load 283
#define NR_syscalls 284
/* user-visible error numbers are in the range -1 - -124: see */
#define __syscall_return(type, res) \
do { \
if ((unsigned long)(res) >= (unsigned long)(-125)) { \
errno = -(res); \
res = -1; \
} \
return (type) (res); \
} while (0)
/* XXX - _foo needs to be __foo, while __NR_bar could be _NR_bar. */
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type,__res); \
}
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1))); \
__syscall_return(type,__res); \
}
#define _syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
__syscall_return(type,__res); \
}
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}
#define _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5))); \
__syscall_return(type,__res); \
}
#define _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4, \
type5,arg5,type6,arg6) \
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5,type6 arg6) \
{ \
long __res; \
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int $0x80 ; pop %%ebp" \
: "=a" (__res) \
: "i" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)), \
"0" ((long)(arg6))); \
__syscall_return(type,__res); \
}
#ifdef __KERNEL__
#define __ARCH_WANT_IPC_PARSE_VERSION
#define __ARCH_WANT_OLD_READDIR
#define __ARCH_WANT_OLD_STAT
#define __ARCH_WANT_STAT64
#define __ARCH_WANT_SYS_ALARM
#define __ARCH_WANT_SYS_GETHOSTNAME
#define __ARCH_WANT_SYS_PAUSE
#define __ARCH_WANT_SYS_SGETMASK
#define __ARCH_WANT_SYS_SIGNAL
#define __ARCH_WANT_SYS_TIME
#define __ARCH_WANT_SYS_UTIME
#define __ARCH_WANT_SYS_WAITPID
#define __ARCH_WANT_SYS_SOCKETCALL
#define __ARCH_WANT_SYS_FADVISE64
#define __ARCH_WANT_SYS_GETPGRP
#define __ARCH_WANT_SYS_LLSEEK
#define __ARCH_WANT_SYS_NICE
#define __ARCH_WANT_SYS_OLD_GETRLIMIT
#define __ARCH_WANT_SYS_OLDUMOUNT
#define __ARCH_WANT_SYS_SIGPENDING
#define __ARCH_WANT_SYS_SIGPROCMASK
#define __ARCH_WANT_SYS_RT_SIGACTION
#endif
#ifdef __KERNEL_SYSCALLS__
#include
#include
#include
#include
/*
* we need this inline - forking from kernel space will result
* in NO COPY ON WRITE (!!!), until an execve is executed. This
* is no problem, but for the stack. This is handled by not letting
* main() use the stack at all after fork(). Thus, no function
* calls - which means inline code for fork too, as otherwise we
* would use the stack upon exit from 'fork()'.
*
* Actually only pause and fork are needed inline, so that there
* won't be any messing with the stack from main(), but we define
* some others too.
*/
static inline _syscall3(int,execve,const char *,file,char **,argv,char **,envp)
asmlinkage int sys_modify_ldt(int func, void __user *ptr, unsigned long bytecount);
asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff);
asmlinkage int sys_execve(struct pt_regs regs);
asmlinkage int sys_clone(struct pt_regs regs);
asmlinkage int sys_fork(struct pt_regs regs);
asmlinkage int sys_vfork(struct pt_regs regs);
asmlinkage int sys_pipe(unsigned long __user *fildes);
asmlinkage int sys_ptrace(long request, long pid, long addr, long data);
asmlinkage long sys_iopl(unsigned long unused);
struct sigaction;
asmlinkage long sys_rt_sigaction(int sig,
const struct sigaction __user *act,
struct sigaction __user *oact,
size_t sigsetsize);
#endif
/*
* "Conditional" syscalls
*
* What we want is __attribute__((weak,alias("sys_ni_syscall"))),
* but it doesn't work on all toolchains, so we just do it by hand
*/
#ifndef cond_syscall
#define cond_syscall(x) asm(".weak\t" #x "\n\t.set\t" #x ",sys_ni_syscall");
#endif
#endif /* _ASM_I386_UNISTD_H_ */
Error Numbers
=============
/usr/include/asm-generic/errno-base.h
-------------------------------------
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#endif
/usr/include/asm-generic/errno.h
--------------------------------
#ifndef _ASM_GENERIC_ERRNO_H
#define _ASM_GENERIC_ERRNO_H
#include
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#endif
syscallw:
; Linux x86 system call entry for Forth programs
; Luke McCarthy 22 April 2004
;
; (arg1) (arg3) (arg3) (arg4) (arg5) argcount callno -- retno
mov [call_no], eax ; save syscall number
mov eax, [esi] ; get argument count
mov [esp_sav], esp ; save esp
lea esp, [esi+4] ; point esp to arguments
lea esi, [4+esi+eax*4] ; set esi pointing past arguments
mov [esi_sav], esi ; and save it
neg eax ; eax (arg count) is subtracted
lea ebx, [syscall0+eax] ; from syscall0
jmp ebx ; to find where to jmp into
pop edi ; arg 5
pop esi ; arg 4
pop edx ; arg 3
pop ecx ; arg 2
pop ebx ; arg 1
syscall0:
mov eax, [call_no] ; load call number
int 0x80 ; call linux... return code in eax
mov esp, [esp_sav] ; restore esp
mov esi, [esi_sav] ; restore esi
ret ; whew!
call_no: dd 0
esp_sav: dd 0
esi_sav: dd 0
My Forth system uses the stack exactly like colorForth, i.e. esp=return
stack, esi=next on data stack, eax=top item of data stack.
I can use it a bit like this:
variable filename text "/path/to/file" zs,
: open 0 3 5 syscall ;
: r/w 2 ; \ IIRC
filename r/w open \ file handle on stack
Look at my version of 'type' (which uses the write system call):
: type >r >r 1 r> r> 3 4 syscall drop ;
A good resource is LinuxAssembly (main site always seems down, Google
has a cache, try the mirror at: http://linuxasm.gerf.org/).
--
Luke McCarthy
(Lurker Extraordinaire)
===================================
As you know, kernels <2.6.10 (or so) barf if they don't have a writeable
section last. This affects asmutils that don't have a "UDATASEG".
Perhaps add one, whether we need it or not, if KERNEL>??? ? Gas gives us
a .data section, whether we asked for one or not... (towards the end of
elf.inc, I guess?)
====================================