Object linking order trouble - c

I am facing linking problem. I'll illustrate it:
a.c:
extern void b(void);
int main() {
a();
return 0;
}
void a() {
b();
}
b.S:
.extern a
b:
jmp a
No matter if I'll link
gcc a.o b.o -o c
or
gcc b.o a.o -o c
I'll get unresolved symbols. How do I link these files? I can't merge them. Example may be nonsensical, but that illustrates point, what do i try to archive.

Initial investigation:
a.c
extern void b(void);
void a(void);
int main() {
a();
return 0;
}
void a() {
b();
}
b.S
.extern a
b:
jmp a
b.c
void a(void);
void b(void)
{
a();
}
Output
$ gcc -c a.c
$ gcc -c b.c -o b_gcc.o
$ as b.S -o b_as.o
$ gcc a.o b_gcc.o -o test_gcc
$ gcc a.o b_as.o -o test_as
a.o: In function `a':
a.c:(.text+0x15): undefined reference to `b'
collect2: error: ld returned 1 exit status
So what gives? Why is it okay with GCC but not GAS?
$ objdump -t b_gcc.o > syms_gcc
$ objdump -t b_as.o > syms_as
$ diff syms_gcc syms_as
2c2
< b_gcc.o: file format elf64-x86-64
---
> b_as.o: file format elf64-x86-64
5d4
< 0000000000000000 l df *ABS* 0000000000000000 b.c
9,12c8
< 0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
< 0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
< 0000000000000000 l d .comment 0000000000000000 .comment
< 0000000000000000 g F .text 000000000000000b b
---
> 0000000000000000 l .text 0000000000000000 b
Okay, so gcc makes b a global symbol. Lets try .global b in b.S:
$ as b.S -o b_as2.o
$ gcc a.o b_as2.o
$
Success. So gcc/ld will do multi-pass symbol resolution for anything that is not in a static library. But it only looks for global symbols. Here's the final b.S:
.extern a
.global b
b:
jmp a

Related

How to link file generated with --relocatable in a PIE executable?

I have a big text file that I want to include in a C program. I could just make it a string literal but it's pretty big and that would be cumbersome. So I'm currently linking like this:
$ ld -r -b binary -o /tmp/stuff.o /tmp/stuff.txt
$ clang -o myprogram main.o /tmp/stuff.o
Objdump output:
$ objdump -t /tmp/stuff.o
/tmp/stuff.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l d .data 0000000000000000 .data
0000000000000006 g *ABS* 0000000000000000 _binary__tmp_stuff_txt_size
0000000000000006 g .data 0000000000000000 _binary__tmp_stuff_txt_end
0000000000000000 g .data 0000000000000000 _binary__tmp_stuff_txt_start
In the code, I do this (gotten from this question):
extern char _binary__tmp_stuff_txt_start[];
extern char _binary__tmp_stuff_txt_size[];
int f(void) {
size_t size = (size_t)_binary__tmp_stuff_txt_size;
do_stuff(size, _binary__tmp_stuff_txt_start);
}
Everything works great, but when I compile with GCC instead of Clang, it segfaults. Looking at it in GDB, the size variable initialized like this size_t size = (size_t)_binary__tmp_stuff_txt_size; is garbage. It seems that when GCC links, it passes the -pie flag to ld but Clang doesn't. I could fix this by just passing -no-pie to GCC, but it seems kindof sad that doing something so simple would prevent using PIE. Is there something I should change to make this work?

How to fix undefined reference linker errors when static linking archive with gcc target_clones function multi-versioning

I am using the target_clones GCC attribute for run-time optimized SIMD versions of several functions, some declared static and others used by other objects in the same static library. The latter have declarations, with the target_clones attribute, in a header file. All of the objects build fine and are assembled into a static archive with ar. The final application linking stage fails though when including the static archive, with undefined reference errors to the versioned symbols for the library public functions.
UPDATE: Added a gcc bug report, in case that is what this is: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91664
I created a test application which illustrates this problem here https://github.com/elementgreen/fmv-test
It has 3 make targets. The first one "make works" just compiles the test app all in one step from .c files. The second "make also_works" compiles each .c file into object .o files and then links those with gcc -o. The third target "make borken" does not work and illustrates the problem. Each .c file is compiled into .o object files, then a static archive is created with ar, then gcc is used to link the static archive into the final application.
Here are the contents of the test application I put on github:
main.c:
#include <stdlib.h>
#include <string.h>
#include "fmv-test.h"
#define ARRAY_SIZE 100000
int
main (int argc, char **argv)
{
double *dArray;
dArray = malloc (ARRAY_SIZE * sizeof (double));
memset (dArray, 0, ARRAY_SIZE * sizeof (double));
fmv_test (dArray, ARRAY_SIZE);
return 0;
}
fmv-test.h:
#ifndef __FMV_TEST_H__
#define __FMV_TEST_H__
#define SIMD_CLONE __attribute__ ((__target_clones__ ("avx2","avx","sse4.1","sse2","default")))
double fmv_test (double *dArray, int size) SIMD_CLONE;
#endif
fmv-test.c:
#include "fmv-test.h"
static void internal_func (double *dArray, int size) SIMD_CLONE;
double
fmv_test (double *dArray, int size)
{
double result;
int i;
internal_func (dArray, size);
for (i = 0; i < size; i++)
result += dArray[i];
return result;
}
static void
internal_func (double *dArray, int size)
{
int i;
for (i = 0; i < size; i++)
dArray[i] += 1.0;
}
Makefile:
works:
#echo "This works"
gcc -o fmv-test fmv-test.c main.c
also_works:
#echo "This also works"
gcc -c fmv-test.c
gcc -c main.c
gcc -o fmv-test fmv-test.o main.o
borken:
#echo "This doesn't work"
gcc -c fmv-test.c
gcc -c main.c
ar cr fmv-test.a fmv-test.o main.o
gcc -o fmv-test-borken fmv-test.a
Linking a static archive with gcc function multi-versioning should work. Instead it fails with the following errors:
/usr/bin/ld: fmv-test.a(main.o): in function `fmv_test':
main.c:(.text.fmv_test.resolver[fmv_test.resolver]+0x1f): undefined reference to `fmv_test.avx2.0'
/usr/bin/ld: main.c:(.text.fmv_test.resolver[fmv_test.resolver]+0x3b): undefined reference to `fmv_test.avx.1'
/usr/bin/ld: main.c:(.text.fmv_test.resolver[fmv_test.resolver]+0x57): undefined reference to `fmv_test.sse4_1.2'
/usr/bin/ld: main.c:(.text.fmv_test.resolver[fmv_test.resolver]+0x71): undefined reference to `fmv_test.sse2.3'
/usr/bin/ld: main.c:(.text.fmv_test.resolver[fmv_test.resolver]+0x7a): undefined reference to `fmv_test.default.4'
collect2: error: ld returned 1 exit status
It seems the object files end up with different numbered endings on the various symbols, but these still manage to link up with the "also_works" target, so I don't think that is the problem (see below output from objdump). Is this a bug in gcc? I'm using version 8.3.0 on Ubuntu 19.04.
objdump -t fmv-test.o | grep fmv_test
0000000000000000 l F .text 0000000000000062 fmv_test.default.9
0000000000000239 l F .text 0000000000000062 fmv_test.avx2.4
000000000000029b l F .text 0000000000000062 fmv_test.avx.5
00000000000002fd l F .text 0000000000000062 fmv_test.sse4_1.6
000000000000035f l F .text 0000000000000062 fmv_test.sse2.7
0000000000000000 l d .text.fmv_test.resolver 0000000000000000 .text.fmv_test.resolver
00000000000003c1 g i .text 0000000000000080 internal_func._GLOBAL___fmv_test.ifunc
0000000000000000 w F .text.fmv_test.resolver 0000000000000080 fmv_test.resolver
0000000000000000 g i .text.fmv_test.resolver 0000000000000080 fmv_test
objdump -t main.o | grep fmv_test
0000000000000000 l d .text.fmv_test.resolver 0000000000000000 .text.fmv_test.resolver
0000000000000000 g i .text.fmv_test.resolver 0000000000000080 fmv_test
0000000000000000 w F .text.fmv_test.resolver 0000000000000080 fmv_test.resolver
0000000000000000 *UND* 0000000000000000 fmv_test.avx2.0
0000000000000000 *UND* 0000000000000000 fmv_test.avx.1
0000000000000000 *UND* 0000000000000000 fmv_test.sse4_1.2
0000000000000000 *UND* 0000000000000000 fmv_test.sse2.3
0000000000000000 *UND* 0000000000000000 fmv_test.default.4

Line numbers in GCC output object file not preserved when linking to ELF

I am trying to build a basic project for ARM with symbols and associated line numbers, so that I can easily debug the project from GDB Multiarch while it is running in QEMU.
I have two files, a C source file and some assembly. In this example, they are very simple:
cmain.c:
int add_numbers(int a, int b) {
return a + b;
}
int cmain() {
int a = 3;
int b = 4;
int c = add_numbers(a, b);
}
main.s:
.section .init
.global _start
_start:
.extern cmain
mov sp, #0x8000
bl cmain
Additionally, here's the linker file, kernel.ld:
SECTIONS {
.init 0x8000 : {
*(.init)
}
.text : {
*(.text)
}
.data : {
*(.data)
*(.bss)
*(.rodata*)
*(.COMMON)
}
/DISCARD/ : {
*(*)
}
}
I then build these projects with debugging symbols using the following shell script. In brief, it assembles and compiles the files into object files, then links them into an ELF and objcopies into an IMG.
rm -r build
mkdir -p build
arm-none-eabi-as -I . main.s -o build/main.o
arm-none-eabi-gcc -ffreestanding -fno-builtin -march=armv7-a -MD -MP -g -c cmain.c -o build/cmain.o
arm-none-eabi-ld build/main.o build/cmain.o -L/usr/lib/gcc/arm-none-eabi/6.3.1/ -lgcc --no-undefined -o build/output.elf -T kernel.ld
arm-none-eabi-objcopy build/output.elf -O binary build/kernel.img --keep-file-symbols
For GDB debugger stepping, I need the ELF to have line numbers for the C source. (Note that the actual project has many more C files.) The lines numbers are present in C object file, but not in the ELF.
$ arm-none-eabi-nm build/cmain.o --line-numbers
00000000 T add_numbers /home/aaron/Desktop/arm-mcve/cmain.c:1
00000030 T cmain /home/aaron/Desktop/arm-mcve/cmain.c:5
$ arm-none-eabi-nm build/output.elf --line-numbers
00008008 T add_numbers
00008038 T cmain
00008000 T _start
Why is there no line number information in the ELF, and how can I add it so that GDB can step through it?
Your linker script discards the sections with debugging information. Look at the default linker script arm-none-eabi-ld --verbose for some ideas. You will at least need some of the DWARF 2 sections:
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
(Adding all of them should work.)

What exactly does `-rdynamic` do and when exactly is it needed?

What exactly does -rdynamic (or --export-dynamic at the linker level) do and how does it relate to symbol visibility as defined by the -fvisibility* flags or visibility pragmas and __attribute__s?
For --export-dynamic, ld(1) mentions:
...
If you use "dlopen" to load a dynamic object which needs to refer back
to the symbols defined by the program, rather than some other dynamic
object, then you will probably need
to use this option when linking the program itself. ...
I'm not sure I completely understand this. Could you please provide an example that doesn't work without -rdynamic but does with it?
Edit:
I actually tried compiling a couple of dummy libraries (single file, multi-file, various -O levels, some inter-function calls, some hidden symbols, some visible), with and without -rdynamic, and so far I've been getting byte-identical outputs (when keeping all other flags constant of course), which is quite puzzling.
Here is a simple example project to illustrate the use of -rdynamic.
bar.c
extern void foo(void);
void bar(void)
{
foo();
}
main.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void foo(void)
{
puts("Hello world");
}
int main(void)
{
void * dlh = dlopen("./libbar.so", RTLD_NOW);
if (!dlh) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
void (*bar)(void) = dlsym(dlh,"bar");
if (!bar) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
bar();
return 0;
}
Makefile
.PHONY: all clean test
LDEXTRAFLAGS ?=
all: prog
bar.o: bar.c
gcc -c -Wall -fpic -o $# $<
libbar.so: bar.o
gcc -shared -o $# $<
main.o: main.c
gcc -c -Wall -o $# $<
prog: main.o | libbar.so
gcc $(LDEXTRAFLAGS) -o $# $< -L. -lbar -ldl
clean:
rm -f *.o *.so prog
test: prog
./$<
Here, bar.c becomes a shared library libbar.so and main.c becomes
a program that dlopens libbar and calls bar() from that library.
bar() calls foo(), which is external in bar.c and defined in main.c.
So, without -rdynamic:
$ make test
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -o prog main.o -L. -lbar -ldl
./prog
./libbar.so: undefined symbol: foo
Makefile:23: recipe for target 'test' failed
And with -rdynamic:
$ make clean
rm -f *.o *.so prog
$ make test LDEXTRAFLAGS=-rdynamic
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -rdynamic -o prog main.o -L. -lbar -ldl
./prog
Hello world
-rdynamic exports the symbols of an executable, this mainly addresses scenarios as described in Mike Kinghan's answer, but also it helps e.g. Glibc's backtrace_symbols() symbolizing the backtrace.
Here is a small experiment (test program copied from here)
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
}
/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
print_trace ();
}
int
main (void)
{
dummy_function ();
return 0;
}
compile the program: gcc main.c and run it, the output:
Obtained 5 stack frames.
./a.out() [0x4006ca]
./a.out() [0x400761]
./a.out() [0x40076d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f026597f830]
./a.out() [0x4005f9]
Now, compile with -rdynamic, i.e. gcc -rdynamic main.c, and run again:
Obtained 5 stack frames.
./a.out(print_trace+0x28) [0x40094a]
./a.out(dummy_function+0x9) [0x4009e1]
./a.out(main+0x9) [0x4009ed]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f85b23f2830]
./a.out(_start+0x29) [0x400879]
As you can see, we get a proper stack trace now!
Now, if we investigate ELF's symbol table entry (readelf --dyn-syms a.out):
without -rdynamic
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free#GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts#GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace_symbols#GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace#GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail#GLIBC_2.4 (3)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf#GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
with -rdynamic, we have more symbols, including the executable's:
Symbol table '.dynsym' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free#GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts#GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace_symbols#GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace#GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail#GLIBC_2.4 (3)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf#GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main#GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
11: 0000000000601060 0 NOTYPE GLOBAL DEFAULT 24 _edata
12: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 24 __data_start
13: 0000000000601068 0 NOTYPE GLOBAL DEFAULT 25 _end
14: 00000000004009d8 12 FUNC GLOBAL DEFAULT 14 dummy_function
15: 0000000000601050 0 NOTYPE WEAK DEFAULT 24 data_start
16: 0000000000400a80 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
17: 0000000000400a00 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
18: 0000000000400850 42 FUNC GLOBAL DEFAULT 14 _start
19: 0000000000601060 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
20: 00000000004009e4 16 FUNC GLOBAL DEFAULT 14 main
21: 00000000004007a0 0 FUNC GLOBAL DEFAULT 11 _init
22: 0000000000400a70 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
23: 0000000000400a74 0 FUNC GLOBAL DEFAULT 15 _fini
24: 0000000000400922 182 FUNC GLOBAL DEFAULT 14 print_trace
I hope that helps!
I use rdynamic to print out backtraces using the backtrace()/backtrace_symbols() of Glibc.
Without -rdynamic, you cannot get function names.
To know more about the backtrace() read it over here.
From The Linux Programming Interface:
42.1.6
Accessing Symbols in the Main Program
Suppose that we use dlopen() to dynamically load a shared library,
use dlsym() to obtain the address of a function x() from that
library, and then call x(). If x() in turn calls a function y(),
then y() would normally be sought in one of the shared libraries
loaded by the program.
Sometimes, it is desirable instead to have x() invoke an
implementation of y() in the main program. (This is similar to a
callback mechanism.) In order to do this, we must make the
(global-scope) symbols in the main program available to the dynamic
linker, by linking the program using the --export-dynamic linker
option:
$ gcc -Wl,--export-dynamic main.c (plus further options and
arguments)
Equivalently, we can write the following:
$ gcc -export-dynamic main.c
Using either of these options allows a dynamically loaded library to
access global symbols in the main program.
The gcc -rdynamic option and the gcc -Wl,-E option are further
synonyms for -Wl,--export-dynamic.
I guess this only works for dynamically loaded shared library, opened with dlopen(). Correct me if I am wrong.

Symbol is lost after linking shared library

Consider the two files listed below:
file a.c
extern int foovar;
int foobarize() {
return foovar * foovar;
}
and file b.c
int foovar = 10;
I compile the static library liba.a and the shared library libb.so as follows:
# liba.a
gcc -fPIC -c a.c -o a.o
ar cr liba.a a.o
ranlib liba.a
# libb.so
gcc -fPIC -c b.c -o b.o
gcc -fPIC -shared -Wl,-soname,libb.so -o libb.so b.o liba.a
Note that the function foobarize defined in a.c is present in liba.a but it isn't present in libb.so. I can guarantee that by issuing the nm program:
$ nm liba.a
a.o:
0000000000000000 T foobarize
U foovar
U _GLOBAL_OFFSET_TABLE_
$ nm libb.so
000000000020088c B __bss_start
000000000020088c b completed.6617
w __cxa_finalize##GLIBC_2.2.5
0000000000000530 t deregister_tm_clones
00000000000005c0 t __do_global_dtors_aux
0000000000200650 t __do_global_dtors_aux_fini_array_entry
0000000000200880 d __dso_handle
0000000000200660 d _DYNAMIC
000000000020088c D _edata
0000000000200890 B _end
0000000000000630 T _fini
0000000000200888 D foovar
0000000000000600 t frame_dummy
0000000000200648 t __frame_dummy_init_array_entry
0000000000000640 r __FRAME_END__
0000000000200858 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000000004d8 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000200658 d __JCR_END__
0000000000200658 d __JCR_LIST__
w _Jv_RegisterClasses
0000000000000570 t register_tm_clones
0000000000200890 d __TMC_END__
How can I get the foobarize function in the libb.so shared library?
What you need:
# force all symbols
gcc -fPIC -shared -Wl,-soname,libb.so -o libb.so b.o \
-Wl,--whole-archive liba.a -Wl,--no-whole-archive
#force just a specific symbol
gcc -fPIC -shared -Wl,-soname,libb.so -o libb.so b.o \
-u foobarize liba.a
Why you need it:
A static library is a simple collection of object files. The one major difference from a bunch of object files is the following: when an undefined symbol needs to be resolved, the library is searched and only the object file that actually defines the symbol is linked. No undefined symbols? Nothing is searched, nothing is linked. To override this default behaviour, the GNU linker implements --whole-archive. Most linkers implement -u to force a particular symbol to be treated as undefinmed.

Resources