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?) ====================================