Getting started with GCC: Difference between revisions

From ICO wiki
Jump to navigationJump to search
Lvosandi (talk | contribs)
No edit summary
Lvosandi (talk | contribs)
No edit summary
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Introduction ==
GCC (GNU Compiler Collection) is a suite of tools aimed at compiling various programming languages for different CPU architectures.
GCC (GNU Compiler Collection) is a suite of tools aimed at compiling various programming languages for different CPU architectures.
There are many competitive alternatives such as [http://llvm.org/ LLVM].
Following instructions can be carried out at <code>enos.itcollege.ee</code> via SSH, the software is already installed there so you don't have to run <code>apt-get install</code> lines.
Following instructions can be carried out at <code>enos.itcollege.ee</code> via SSH, the software is already installed there so you don't have to run <code>apt-get install</code> lines.
On your personal Ubuntu machine you can install GCC via APT:
On your personal Ubuntu machine you can install GCC via APT:


  apt-get install build-essential
sudo apt-get install build-essential
 
In order to compile binaries for foreign architecure you need cross-compiler. In Ubuntu repositories you may find ARM cross compilers:
 
sudo apt-get install g++-arm-linux-gnueabihf g++-arm-linux-gnueabi
 
The point of this article is to get familiar with tools provided by GCC toolchain.
GCC, especially as a cross-compiler is widely used to compile firmware for [[Getting started with Raspberry Pi|Raspberry Pi]], Arduino, Ubuntu, routers and other embedded devices.
You can also manually compile a compiler for foreign architectures which isn't supported out of the box for your distribution,
see [http://lauri.vosandi.com/tub/computer-architecture/building-mips-toolchain.html here] for more information
 
[[File:Compiling-compilers.jpg]]
 
It's also possible to run foreign binaries if you install QEMU emulation layer and binary detection support:


sudo apt-get install qemu-user-static binfmt-support


==Sample source files==
==Sample source files==
Line 10: Line 28:
Create a plain-text file <code>libfib.c</code> with following content:
Create a plain-text file <code>libfib.c</code> with following content:


  int fib(n) {
int fib(n) {
      if (n == 1 || n== 2) {
    if (n == 1 || n== 2) {
          return 1;
        return 1;
      } else {
    } else {
          return fib(n-1) + fib(n-2);
        return fib(n-1) + fib(n-2);
      }
    }
  }
}


Create another plain-text file to invoke the function described above:
Create another plain-text file <code>hello.c</code> to invoke the function described above:


  #include <stdio.h>
#include <stdio.h>
   
   
  int main(int argc, char **argv) {
int main(int argc, char **argv) {
    if (argc != 2) {
    if (argc != 2) {
      printf("Please specify only one argument\n");
        printf("Please specify only one argument\n");
      return -1;
        return -1;
    } else {
    } else {
      int n;
        int n;
      sscanf("%d", argv[1], &n);
        sscanf(argv[1], "%d", &n);
      printf("%dth fibonacci number is %d\n", n, fib(n));
        printf("%dth fibonacci number is %d\n", n, fib(n));
      return 0;
        return 0;
    }
    }
  }
}


== Compiling native binaries ==
== Compiling native binaries ==
Line 38: Line 56:
Verify that the code works:
Verify that the code works:


  gcc libfib.c hello.c -o hello-simple
gcc libfib.c hello.c -o hello-simple


Invocation of the binary should result in output <code>42th fibonacci number is 267914296</code>:
Invocation of the binary should result in output <code>42th fibonacci number is 267914296</code>:


  ./hello-simple 42
./hello-simple 42


Use <code>file</code> to inspect what kind of executable was created:
Use <code>file</code> to inspect what kind of executable was created:


  file hello-simple
file hello-simple


== Compiling assembly ==
== Compiling assembly ==
Line 52: Line 70:
Generate assembly for the C file which contains <code>fib</code> function:
Generate assembly for the C file which contains <code>fib</code> function:


  gcc libfib.c -S -o libfib.s
gcc libfib.c -S -o libfib.s


Investigate the assembly corresponding to <code>fib</code>:
Investigate the assembly corresponding to <code>fib</code>:


  gcc -static libfib.c hello.c -o hello-simple-static
cat libfib.s
 
 


== Dynamic vs static linking ==
== Dynamic vs static linking ==
Line 64: Line 80:
The <code>hello-simple</code> compiled above is dynamically linked library, you can list dependant libraries width <code>ldd</code>:
The <code>hello-simple</code> compiled above is dynamically linked library, you can list dependant libraries width <code>ldd</code>:


  ldd hello-simple
ldd hello-simple


Compile the static version:
Compile the static version:


  gcc -static libfib.c hello.c -o hello-simple-static
gcc -static libfib.c hello.c -o hello-simple-static


Attempting to run <code>ldd</code> against such binary will result in error <code>not a dynamic executable</code>.
Attempting to run <code>ldd</code> against such binary will result in error <code>not a dynamic executable</code>.
List the files and their sizes, what is the difference and why?
List the files and their sizes, what is the difference and why?


  ls -lah hello-simple hello-simple-static
ls -lah hello-simple hello-simple-static




Line 80: Line 96:
Use the compiler to compile assembly from the C source code:
Use the compiler to compile assembly from the C source code:


  gcc -fPIC libfib.c -S -o libfib-native.s
gcc -fPIC libfib.c -S -o libfib-native.s
  gcc hello.c -S -o hello-native.s
gcc hello.c -S -o hello-native.s


Turn assembly into object files:
Turn assembly into object files:


  gcc -fPIC -c libfib-native.s -o libfib-native.o
gcc -fPIC -c libfib-native.s -o libfib-native.o
  gcc -c hello-native.s -o hello-native.o
gcc -c hello-native.s -o hello-native.o


Finally link the object file against system libraries to produce usable binary:
Finally link the object file against system libraries to produce usable binary:


  mkdir mylibs
mkdir mylibs
  gcc -fPIC libfib-native.o -shared -o mylibs/libfib-native.so
gcc -fPIC libfib-native.o -shared -o mylibs/libfib-native.so
  gcc hello-native.o -o hello-native -L mylibs -l fib-native
gcc hello-native.o -o hello-native -L mylibs -l fib-native


Invoke the binary to test if it actually works, expect the program to print line <code>Hello, World!</code>:
Invoke the binary to test if it actually works, expect the program to print line <code>Hello, World!</code>:


  LD_LIBRARY_PATH=mylibs ./hello-native 42
LD_LIBRARY_PATH=mylibs ./hello-native 42


The binary was compiled dynamically which means that during it's invocation OS is requested to load dependent libraries to the memory is necessary.
The binary was compiled dynamically which means that during it's invocation OS is requested to load dependent libraries to the memory is necessary.
You can use <code>ldd</code> to investigate which libraries are required to invoke the binaries:
You can use <code>ldd</code> to investigate which libraries are required to invoke the binaries:


  LD_LIBRARY_PATH=mylibs ldd hello-native
LD_LIBRARY_PATH=mylibs ldd hello-native


Alternatively you can compile the binary statically, in that case all the necessary machinery get's bundled into the resulting binary:
Alternatively you can compile the binary statically, in that case all the necessary machinery get's bundled into the resulting binary:


  gcc libfib-native.o hello-native.o -static -o hello-native-static
gcc libfib-native.o hello-native.o -static -o hello-native-static




Line 112: Line 128:
Use <code>objdump</code> to locate the section corresponding to function <code>fib</code>.
Use <code>objdump</code> to locate the section corresponding to function <code>fib</code>.


  objdump -D mylibs/libfib-native.so
objdump -D mylibs/libfib-native.so




Line 119: Line 135:
Use ARM cross-compiler to compile ARM binaries on x86:
Use ARM cross-compiler to compile ARM binaries on x86:


  arm-linux-gnueabihf-gcc hello.c -static -o hello
arm-linux-gnueabihf-gcc libfib.c hello.c -static -o hello-foreign
 
Use <code>file</code>, <code>ldd</code> and <code>objdump</code> to inspect the binary.
Assuming <code>binfmt-support</code> has been set up properly and QEMU emulation layer is available a static ARM binary can be invoked on x86 host via emulation:
 
./hello-foreign
 
For dynamically linked binaries you need also dependant libraries, the simplest way is to bootstrap a [http://lauri.vosandi.com/cfgmgmt/linux-containers.html#voora-arhitektuuriga-konteinerid LXC container for ARM].
 
 
 
== Makefiles ==
 
Makefiles are used to simplify building a software project.
[http://ant.apache.org/ Ant] is a corresponding tool for Java projects.
[https://maven.apache.org/ Maven] complements Ant with automatic JAR downloading capabilities.
Python does dependency tracking already internally, <code>pip</code> also installs dependant libraries if necessary.
 
Create file <code>Makefile</code>:
 
CC=$(CROSSCOMPILE)gcc      # Make cross-compilation easy
CFLAGS=-fPIC              # Create position independent code, so it would be possible to generate .so libraries
hello: libfib.o hello.o
$(CC) -static -o $@ $?
%.so:%.o
$(CC) -shared -o $@ $?
%.o:%.s
$(CC) -c -o $@ $?
%.s: %.c
$(CC) -S -o $@ $?
clean:
rm -Rfv *.s *.o *.so
 
Invoke the command in the directory that contains the Makefile:
 
make
 
To clean binaries:
 
make clean
 
To cross-compile:
 
CROSSCOMPILE=arm-linux-gnueabihf- make hello
 
Generate ARM assembly for the function of calculating Fibonacci numbers:
 
CROSSCOMPILE=arm-linux-gnueabihf- make libfib.s


./a.out
Store it for later:
Hello, World!
lvosandi@enos:~$ cat  hello.c


cat libfib.s


lvosandi@enos:~$ arm-linux-gnueabihf-gcc -march=armv8-a hello.c  -static -O hello
If most of this make sense so far, try to do some [https://wiki.itcollege.ee/index.php/Category:I600_Introduction_to_Computers_and_Informatics#Assignment:_Investigating_compilers exercises]. ARM mnemonics and all relevant links are there.
arm-linux-gnueabihf-gcc: error: hello: No such file or directory
lvosandi@enos:~$ arm-linux-gnueabihf-gcc -march=armv8-a hello.c  -static -o hello
lvosandi@enos:~$ ./hello
Hello, World!
lvosandi@enos:~$ file hello
hello: ELF 32-bit LSB  executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=f553117e8aa8d9d9e26ae85b29437b5193a1790a, not stripped

Latest revision as of 14:00, 13 October 2016

Introduction

GCC (GNU Compiler Collection) is a suite of tools aimed at compiling various programming languages for different CPU architectures. There are many competitive alternatives such as LLVM. Following instructions can be carried out at enos.itcollege.ee via SSH, the software is already installed there so you don't have to run apt-get install lines. On your personal Ubuntu machine you can install GCC via APT:

sudo apt-get install build-essential

In order to compile binaries for foreign architecure you need cross-compiler. In Ubuntu repositories you may find ARM cross compilers:

sudo apt-get install g++-arm-linux-gnueabihf g++-arm-linux-gnueabi

The point of this article is to get familiar with tools provided by GCC toolchain. GCC, especially as a cross-compiler is widely used to compile firmware for Raspberry Pi, Arduino, Ubuntu, routers and other embedded devices. You can also manually compile a compiler for foreign architectures which isn't supported out of the box for your distribution, see here for more information

It's also possible to run foreign binaries if you install QEMU emulation layer and binary detection support:

sudo apt-get install qemu-user-static binfmt-support

Sample source files

Create a plain-text file libfib.c with following content:

int fib(n) {
    if (n == 1 || n== 2) {
        return 1;
    } else {
        return fib(n-1) + fib(n-2);
    }
}

Create another plain-text file hello.c to invoke the function described above:

#include <stdio.h>

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Please specify only one argument\n");
        return -1;
    } else {
        int n;
        sscanf(argv[1], "%d", &n);
        printf("%dth fibonacci number is %d\n", n, fib(n));
        return 0;
    }
}

Compiling native binaries

Verify that the code works:

gcc libfib.c hello.c -o hello-simple

Invocation of the binary should result in output 42th fibonacci number is 267914296:

./hello-simple 42

Use file to inspect what kind of executable was created:

file hello-simple

Compiling assembly

Generate assembly for the C file which contains fib function:

gcc libfib.c -S -o libfib.s

Investigate the assembly corresponding to fib:

cat libfib.s

Dynamic vs static linking

The hello-simple compiled above is dynamically linked library, you can list dependant libraries width ldd:

ldd hello-simple

Compile the static version:

gcc -static libfib.c hello.c -o hello-simple-static

Attempting to run ldd against such binary will result in error not a dynamic executable. List the files and their sizes, what is the difference and why?

ls -lah hello-simple hello-simple-static


Libraries

Use the compiler to compile assembly from the C source code:

gcc -fPIC libfib.c -S -o libfib-native.s
gcc hello.c -S -o hello-native.s

Turn assembly into object files:

gcc -fPIC -c libfib-native.s -o libfib-native.o
gcc -c hello-native.s -o hello-native.o

Finally link the object file against system libraries to produce usable binary:

mkdir mylibs
gcc -fPIC libfib-native.o -shared -o mylibs/libfib-native.so
gcc hello-native.o -o hello-native -L mylibs -l fib-native

Invoke the binary to test if it actually works, expect the program to print line Hello, World!:

LD_LIBRARY_PATH=mylibs ./hello-native 42

The binary was compiled dynamically which means that during it's invocation OS is requested to load dependent libraries to the memory is necessary. You can use ldd to investigate which libraries are required to invoke the binaries:

LD_LIBRARY_PATH=mylibs ldd hello-native

Alternatively you can compile the binary statically, in that case all the necessary machinery get's bundled into the resulting binary:

gcc libfib-native.o hello-native.o -static -o hello-native-static


Deassembling a binary

Use objdump to locate the section corresponding to function fib.

objdump -D mylibs/libfib-native.so


Cross-compiling

Use ARM cross-compiler to compile ARM binaries on x86:

arm-linux-gnueabihf-gcc libfib.c hello.c -static -o hello-foreign

Use file, ldd and objdump to inspect the binary. Assuming binfmt-support has been set up properly and QEMU emulation layer is available a static ARM binary can be invoked on x86 host via emulation:

./hello-foreign

For dynamically linked binaries you need also dependant libraries, the simplest way is to bootstrap a LXC container for ARM.


Makefiles

Makefiles are used to simplify building a software project. Ant is a corresponding tool for Java projects. Maven complements Ant with automatic JAR downloading capabilities. Python does dependency tracking already internally, pip also installs dependant libraries if necessary.

Create file Makefile:

CC=$(CROSSCOMPILE)gcc      # Make cross-compilation easy
CFLAGS=-fPIC               # Create position independent code, so it would be possible to generate .so libraries

hello: libfib.o hello.o
	$(CC) -static -o $@ $?

%.so:%.o
	$(CC) -shared -o $@ $?

%.o:%.s
	$(CC) -c -o $@ $?

%.s: %.c
	$(CC) -S -o $@ $?

clean:
	rm -Rfv *.s *.o *.so

Invoke the command in the directory that contains the Makefile:

make

To clean binaries:

make clean

To cross-compile:

CROSSCOMPILE=arm-linux-gnueabihf- make hello

Generate ARM assembly for the function of calculating Fibonacci numbers:

CROSSCOMPILE=arm-linux-gnueabihf- make libfib.s

Store it for later:

cat libfib.s

If most of this make sense so far, try to do some exercises. ARM mnemonics and all relevant links are there.