sudo apt install gcc g++ binutils make autoconf cmake
- compile & link the app in one go using
gcc -o hello hello.c
. - run
./hello
.
- compile only first:
gcc -c -o hello.o hello.c
hello.o
is not an executable! (try running with./hello.o
).- We need a seperate stage, called "linking". This stage transforms a compiled binary into an executable. Let's try:
gcc -o hello hello.o
+./hello
. - Some LL comparison of
hello.o
andhello
, using:nm
(symbols list),objdump -d
(machine code).readelf
,file
- What is the difference between
.o
and executables?
rm hello hello.o
strace -s 1000 -f -o trace -e execve gcc -o hello hello.c
vim trace
notice the cc1
compiler and ld
the linker.
gcc -o hello hello_secret.c
fails, butgcc -c -o hello.o hello_secret.c
succeeds. Why? clue:objdump -t hello.o
- Let's build the dependency:
gcc -c -o secret.o secret.c
gcc -o hello secret.o hello.o
./hello
create a static lib:
ar cr libsecret.a secret.o
ar t libsecret.a
rm secret.o
and use when building the app:
gcc -o hello hello.o -L. -lsecret
lets examine the last step with strace:
strace -s 1000 -e execve -f -o trace gcc -o hello hello.o -L. -lsecret
vim trace
and the resulting file:
nm hello
objdump -d hello
gcc -c -o secret.o secret.c
ld -shared -o libsecret.so secret.o
nm libsecret.so
objdump -d libsecret.so
and using it in the app. But first, lets look at the list of runtime dependencies of hello
:
ldd hello
and build hello
:
gcc -o hello hello.o -L. -lsecret
ldd hello
LD_LIBRARY_PATH=$(pwd) ldd hello
lets examine hello
:
nm hello
objudmp -d hello
Using gcc
and ld
all the time is not that nice, isn't it?
Let's automate this a bit by creating a Makefile.
- Take a look at
Makefile
. What do you see? - Build the app:
make clean
make
- run again:
make
- re-save
secret.c
, and run again:make
- run each of
./hello
,./hello_s_static
andLD_LIBRARY_PATH=$(pwd) ./hello_secret
. - Lets trace what happens when running
make
:
make clean
strace -s 1000 -f -o trace -e execve make
vim trace
Maintaining big Makefiles isn't cool either (even though we couldv'e done the previous one better). That's why people came up with Makefile generators.
- Check out
configure.ac
andMakefile.am
. - Create a configure script:
autoreconf -i
. Examine resultant script:vim ./configure
. - Use the
./configure
to generate a Makefile:./configure
. Examine resultant Makefile:vim Makefile
- Build the app using good old make:
make
. - run
./hello_secret
- Trace what happens when running
make
:
make clean
strace -s 1000 -f -o trace -e execve make
vim trace
CMake is fancier and more modern makefile-generator for c/c++.
- Examine the
CMakeLists.txt
:vim CMakeLists.txt
. What do you see? - build the app:
mkdir build
cd build
cmake ..
Take a moment to examine the resultant Makefile: vim Makefile
.
Proceed to build the app: make
. Note the different stages in the output.
-
inspect the different files produced:
libSecretStatic.a
usingar t libSecretStatic.a
libSecretDynamic.so
usingnm libSecretDynamic.so
andobjdump -d libSecretDynamic.so
HelloSecretStatic
usingldd HelloSecretStatic
,nm HelloSecretStatic
,ldd HelloSecretStatic
.HelloSecretDynamic
usingldd HelloSecretDynamic
,nm HelloSecretDynamic
,ldd HelloSecretDynamic
.
-
Trace what happens when running
make
:
make clean
strace -s 1000 -f -o trace -e execve make
vim trace
Ubuntu provides lots and lots of pre-built packages, both dynamic (shared objects, to be used when running an executable) and static (archives, to link statically during build).
- Take a look at the folder
/usr/lib/x86_64-linux-gnu/
usingls
. - Install the dev-packages of
zlib
withsudo apt install zlib1g-dev
. - Examine:
ls -al /usr/lib/x86_64-linux-gnu/libz*
. - What is inside
zlib.a
? check usingar t /usr/lib/x86_64-linux-gnu/libz.a
. - What does
libz.so
provide? check usingreadelf -s /usr/lib/x86_64-linux-gnu/libz.so
. - How can those
zlib
be used when building an app now?
Used to build applications for an architecture different that of the build machine. For example, building on a Ubuntu x86_64 machine to run on RapberryPi, ARM based device. Or Xbox 360, or an Android Phone.
For that one needs: A cross-compiler toolchain, that produces target-based binaries.
Lets install one for ARM: sudo apt-get install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi binutils-arm-linux-gnueabi
.
- Lets build
hello
for an ARM cpu, directly using the cross-compiler:
arm-linux-gnueabi-gcc -o hello-arm hello.c
file hello-arm
./hello-arm
- Doing cross-compilation with autoconf is very easy, and a matter of supplyin the target to the
./configure
script:
make clean
make distclean
./configure --build x86_64-pc-linux-gnueabi --host arm-linux-gnueabi
make
file hello_secret
Rerun with strace to see the cross-compiler in use:
make clean
strace -s 1000 -f -o trace -e execve make
vim trace
- Doing this with
CMake
is actually not much different than the usual, and is a matter of only specifying which gcc toolchain to use:
rm -rf build
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../arm.cmake ..
Run and inspect the CLIs being used:
make clean
strace -s 1000 -f -o trace -e execve make
vim trace
Inspect the resultant file with arm-linux-gnueabi-objdump -d HelloSecretStatic
(why objdump
wouldn't work?). Notice the different instruction set.