The Unix C programming and build environment is typically composed of at least the following commands:
gcc
gdb
make
A file called 'makefile' or 'Makefile' in a source directory is used by the make command, describing the relationships between files and the necessary commands to update/create said files.
The relationships are defined as targets which each have requirements and a recipe to build said targets. When make builds a target it look at the requirements and if any requirement is newer than the target, or the target is missing, then the target will be re-made using the recipe.
Started with # and extend to EOL. Use # for a literal # sign
Roughly equal to sh/bash variable definitions: i.e.:
name=value or name := value
=
:=
Variables are used in recipes as $(name)
$(
)
Wherever variables can be used you may use "functions", usually in the form of: $(func-name comma-deliminated-params)
Example:
$(sort $(objs)) ──► Emits a sorted version of $objs
$(sort $(objs))
$objs
These are special commands, such as:
include file reads the "file" as a sub-makefile.
include file
include
conditional statements [ else statements ] endif
else
endif
conditional true if: ifeq (arg1,arg2) arg1 and arg2 are the same ifeq (arg1,) arg1 is not empty ifneq (arg1,arg2) arg1 and arg2 are not the same ifdef var var is defined ifndef var var is not defined
ifeq (arg1,arg2)
ifeq (arg1,)
ifneq (arg1,arg2)
ifdef var
ifndef var
target(s): prerequisites [recipe]
targets One or more files, separated by spaces to make. You may use wild-cards for the file-names. If the target already exists, but the prerequisites have a newer timestamp than the target, it will be re-made so it is up to date. prerequisites These are files that are required by the target to be made usually source/header files or some generated source output from a code generation tool such as lex/flex and yacc/bison recipe How to make the target. Often times make will know how to do this via its stored implicit rules and not require an explicit recipe. Must be prefixed by a tab character. Recipe lines are sh shell commands. If the recipe needs to span more than one line, you should escape the newline character with a backslash () (not necessarily necessary.)
Examples:
# target foo.o requires both foo.c and bar.h (bar.h is included by foo.c) # '$(CC) -c foo.c' is the recipe to make the target foo.o foo.o: foo.c bar.h $(CC) -c foo.c
# Makes more than one program, each with their own C source file(s). Assumes # you don't want to build any .o files, really only suitable for small programs all: prog1 prog2 prog1: prog1.c prog1.h $(CC) $(CFLAGS) -o prog1 prog1.c $(LDFLAGS) prog2: prog2.c foo.c prog2.h $(CC) $(CFLAGS) -o prog2 prog2.c foo.c $(LDFLAGS)
make [-C dir] [-f makefile] [-j #jobs] [ target ]
-C
-f
-j
-C dir Move to dir then begin making -f makefile Use makefile instead of the default -j #jobs Parallel make using #jobs target The target within the makefile to build. If not specified the first target found in the makefile is used
MAKEFLAGS
> setenv MAKEFLAGS '-j32'
> export MAKEFLAGS='-j32'
# A "variable" defines the CC variable to be gcc (the C compiler): CC=gcc # Rule to make the executable hello from hello.o, hello.o will be made first: hello: hello.o $(CC) -o hello hello.o # Rule makes hello.o from hello.c: hello.o: hello.c $(CC) -c hello.c
CC=gcc -std=c11 CFLAGS=-ggdb -Wall LIBS=-lcurses # Usually the first rule is used to define what should be done when just 'make' # is invoked: all: c1 c2 c3 c4 f1 f2 f3 util.o: util.c $(CC) $(CFLAGS) -c util.c # % in a target/prerequisite is like a wild-card. # $@ -> The filename of the target # $< -> The name of the first prerequisite c%: c%.c util.o $(CC) $(CFLAGS) -o $@ util.o $< $(LIBS) f%: f%.c $(CC) $(CFLAGS) -o $@ $< # Additional rules to clean things up. I.e. 'make clean' / 'make cleanall' clean: rm -f *.o cleanall: clean rm -f c1 c2 c3 c4 f1 f2 f3
The TUI mode represents a nice curses interface to gdb.
Compile your programs with:
gcc -ggdb
clang -gdwarf
nasm -g -F dwarf -felf64 prog.s
ld -o prog prog.o
Then issue the command: gdb prog
Then inside of GDB issue the following commands:
tui enable tui reg general break _start run [<params>]
Then use 'step', 'next', 'si', 'ni', etc as normal, but enjoy the curses window. The source window should be the selected one, which will respond to arrow keys and scroll wheel events to move around. The enter key will repeat the last command run over and over.
To direct the output of the program to a different terminal window (because you will not see the output because of the curses nature of the tui window,) use "tty" in the terminal you want to direct the output to, then in the gdb window use "tty ", such as "tty /dev/pts/4" for example, then "run" the program (re-run it if using debug.)
gdb program
-p pid - Debug the already running program with PID 'pid' -c core - Use the core file as the processes memory.
-p
-c
Use:
unlimit coredump (tcsh) ulimit -c unlimited (bash)
unlimit coredump
ulimit -c unlimited
to enable core-dumps.
run
c
next
step
list
:
break
catch
watch
awatch
print
where/bt
up
down
help
quit
info registers
i r
info frame
i f
print $reg
p $reg
print *(char **)($rsp+8)
display /3i $pc
ni
si
x /8g $rsp
In a computer, memory is a sequence of bytes each with a numeric address, exactly analogous to an array of bytes. The index of the array is the address of a byte value:
char memory[size-of-total-memory];
char memory[
];
memory[address] = value
In Linux, a processes memory is laid out like:
┌─────────────────────────┐ 0xFFFFFFFF │ Kernel mode space (1GB) │ <- not directly accessible by a user-space process. ├─────────────────────────┤ 0xC0000000 │ │ │ User mode space (3GB) │ │ │ └─────────────────────────┘ 0x00000000
64 bit (48 bits physical) virtual address space layout is similar, with kernel space at the top 128TB and user space at the beginning 128TB. Currently address bits 48-64 must copy bit 47 (i.e. all ones or zeros) otherwise are non-canonical addresses.
┌─────────────────────────┐ 0xFFFFFFFF FFFFFFFF │ │ │ Kernel mode space │ (128TB) │ │ ├─────────────────────────┤ 0xFFFF8000 00000000 │ │ │ Unused space │ │ │ ├─────────────────────────┤ 0x00007FFF FFFFFFFF │ │ │ User mode space │ (128TB) │ │ └─────────────────────────┘ 0x00000000 00000000
┌──────────────────────────┐ 0xFFFFFFFF │ Kernel mode space (1GB) │ ├──────────────────────────┤ 0xC0000000 │ Random Stack offset │ ├──────────────────────────┤ │ Stack (Grows down) │ RLIMIT_STACK (8MB) ├──────────────────────────┤ │ │ Random MMap offset ├──────────────────────────┤ │ Memory mapping segment │ │ │ File mappings(dyn libs)│ │ │ Anon mappings ▽ │ ├──────────────────────────┤ │ │ program break ├──────────────────────────┤ brk │ △ │ │ │ Heap │ ├──────────────────────────┤ start_brk │ │ Random brk offset ├──────────────────────────┤ │ BSS Segment (uninitial- │ │ ized static vars) Zero- │ │ Filled │ ├──────────────────────────┤ │ Data Segment (static │ end_data │ Variables initilized by │ │ programmer. │ start_data ├──────────────────────────┤ │ Text Segment (ELF) │ end_code │ Binary image of program │ ├──────────────────────────┤ 0x08048000 (start of program) └──────────────────────────┘ 0x00000000
The distinction is mostly down to which privilege level (called a ring) the code runs at:
Can do anything, sees all of memory.
Not used, can access some privileged memory, but not allowed some instructions. Meant for separating device drivers out of the kernel proper, sometimes used by VMs such as VirtualBox.
Cannot change it's own ring (obviously.)
Cannot modify it's own page tables (i.e how it sees memory)
Cannot register interrupt handlers.
Cannot do I/O instructions like IN or OUT
Must basically let the kernel manage things for it. It "communicates" with the kernel by raising an interrupt or syscall which jumps to a specific area of memory in kernel space while switching to ring 0. The kernel figures out what the user-space process wants it to do based on the values in specific registers.
> size binary
> size
> readelf -a binary
> readelf -a
> objdump -d binary
> objdump -d
-S
> cat /proc/*/maps