Merging dedicated ELF sections from libraries into application dedicated ELF section - c

Here is my test. I have a main application composed of the sources main.c and misc.c and a static library made of lib.c.
GOAL: I want to declare all my struct module declarations in an ELF section .modules.
ISSUE: I can only see the struct module declarations from the main application. Here is the output I can see with the following code:
Hello World
- module:module_a
- module:module_b
If I call my_lib() into main() then I see:
Hello World
MyLib
- module:module_a
- module:module_b
- module:module_lib
But I am not interested to directly call module's function into my main application.
CMakeLists.txt
add_executable(main main.c misc.c)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
set(LINKER_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/linker.ld")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T ${LINKER_SCRIPT}")
add_library(static_lib STATIC lib.c)
target_link_libraries(main static_lib)
main.c
#include "module.h"
extern const struct module modules_start[];
extern const struct module modules_end[];
struct module __attribute__ ((section (".modules"))) module_a = {
.name = "module_a",
};
int main(void) {
puts("Hello World");
const struct module *m = modules_start;
while (m < modules_end) {
printf("- module:%s\n", m->name);
m++;
}
return 0;
}
misc.c
#include "module.h"
struct module __attribute__ ((section (".modules"))) module_b = {
.name = "module_b",
};
module.h
#include <stdio.h>
struct module {
const char* name;
};
lib.c
#include "module.h"
struct module __attribute__ ((section (".modules"))) __attribute__ ((used)) module_lib = {
.name = "module_lib",
};
int my_lib(void) {
puts("MyLib");
return 0;
}
linker.ld
SECTIONS
{
.modules : {
modules_start = .;
KEEP(*(.modules))
modules_end = .;
}
}
INSERT AFTER .rodata;
Here are some ELF information:
$ readelf --sections libstatic_lib.a | grep -A 1 modules
[ 5] .modules PROGBITS 0000000000000000 00000058
0000000000000008 0000000000000000 WA 0 0 8
[ 6] .rela.modules RELA 0000000000000000 00000278
0000000000000018 0000000000000018 I 13 5 8
$ readelf --sections main | grep -A 1 modules
[17] .modules PROGBITS 00000000000009c0 000009c0
0000000000000010 0000000000000000 WA 0 0 8
$ nm libstatic_lib.a | grep module
0000000000000000 D module_lib
$ nm main | grep module
00000000000009c0 D module_a
00000000000009c8 D module_b
00000000000009d0 D modules_end
00000000000009c0 D modules_start

If there are no references to an object file in a static library, by default that object file is not included in the link. With the binutils linker, you can disable this optimization using the --whole-archive option.

Related

Why would the .text section of my object file be empty?

Question
I'm compiling the following code using catkin_make. I have a c file schedule_wrappers.c and two c++ files deadline_thread.cpp, stepper_motor.cpp that are being used to create an executable in cmake with the following line:
add_executable(stepper_motor src/stepper_motor.cpp src/deadline_thread.cpp src/schedule_wrappers.c)
The stepper_motor.cpp file produces a "reasonable" .o file (the objdump produces output I would expect), but when cmake gets to the link step it says:
/usr/bin/ld: CMakeFiles/stepper_motor.dir/src/stepper_motor.cpp.o: in function `StepperMotorTB6600::run()':
/home/spovilus/catkin_ws/src/beginner_tutorials/include/stepper_motor_TB6600.hpp:73: undefined reference to `wrapper_sched_yield'
/usr/bin/ld: CMakeFiles/stepper_motor.dir/src/stepper_motor.cpp.o: in function `Motor::Motor(int, int)':
/home/spovilus/catkin_ws/src/beginner_tutorials/include/motor.hpp:43: undefined reference to `DeadlineThread::DeadlineThread(int, int)'
/usr/bin/ld: CMakeFiles/stepper_motor.dir/src/stepper_motor.cpp.o:(.data.rel.ro._ZTI5Motor[_ZTI5Motor]+0x18): undefined reference to `typeinfo for DeadlineThread'
I tried to objdump the filed with objdump -t ./build/beginner_tutorials/CMakeFiles/stepper_motor.dir/src/schedule_wrappers.c.o and got:
./build/beginner_tutorials/CMakeFiles/stepper_motor.dir/src/schedule_wrappers.c.o: file format elf64-littleaarch64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 schedule_wrappers.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .debug_info 0000000000000000 .debug_info
0000000000000000 l d .debug_abbrev 0000000000000000 .debug_abbrev
0000000000000000 l d .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l d .debug_line 0000000000000000 .debug_line
0000000000000000 l d .debug_str 0000000000000000 .debug_str
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .comment 0000000000000000 .comment
and I'm very confused as to why the .text section is empty.
The compiler command cd /home/spovilus/catkin_ws/build/beginner_tutorials && /usr/bin/cc -DROSCONSOLE_BACKEND_LOG4CXX -DROS_BUILD_SHARED_LIBS=1 -DROS_PACKAGE_NAME=\"beginner_tutorials\" -I/home/spovilus/catkin_ws/devel/include -I/home/spovilus/catkin_ws/src/beginner_tutorials/include -I/opt/ros/noetic/include -I/opt/ros/noetic/share/xmlrpcpp/cmake/../../../include/xmlrpcpp -I/home/spovilus/catkin_ws/src/beginner_tutorials/yaml-cpp/include -I/home/spovilus/catkin_ws/src/beginner_tutorials/boost/units/include -O2 -g -DNDEBUG -o CMakeFiles/stepper_motor.dir/src/schedule_wrappers.c.o -c /home/spovilus/catkin_ws/src/beginner_tutorials/src/schedule_wrappers.c emits warnings:
/home/spovilus/catkin_ws/src/beginner_tutorials/src/schedule_wrappers.c: In function ‘wrapper_sched_set_deadline’:
/home/spovilus/catkin_ws/src/beginner_tutorials/src/schedule_wrappers.c:35:7: warning: implicit declaration of function ‘perror’ [-Wimplicit-function-declaration]
35 | perror("Unable to get scheduler attributes");
| ^~~~~~
/home/spovilus/catkin_ws/src/beginner_tutorials/src/stepper_motor.cpp: In function ‘void positionCallback(const ConstPtr&)’:
/home/spovilus/catkin_ws/src/beginner_tutorials/src/stepper_motor.cpp:34:23: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘const ConstPtr’ {aka ‘const boost::shared_ptr<const beginner_tutorials::RelativePosition_<std::allocator<void> > >’} [-Wformat=]
34 | printf("position %d\n",position);
| ~^
| |
| int
so I have to assume it'd doing something.
Source code
/**
* #file schedule_wrappers.c
* #author Sam Povilus (povilus#povil.us)
* #brief A file to wrap Linux Scheduler calls
* #version 0.1
* #date 2023-01-07
*
* #copyright Copyright (c) 2023
*
*/
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/sched/types.h>
#include "schedule_wrappers.h"
static int wrapper_sched_getattr(pid_t pid, struct sched_attr *attr,
unsigned int size, unsigned int flags)
{
return syscall(__NR_sched_getattr, pid, attr, size, flags);
}
static int wrapper_sched_set_deadline(int deadline)
{
struct sched_attr attr;
int ret = wrapper_sched_getattr(0, &attr, sizeof(attr), 0);
if (ret < 0)
{
perror("Unable to get scheduler attributes");
// throw scheduler_exception;
}
attr.sched_policy = SCHED_DEADLINE;
attr.sched_deadline = deadline;
return syscall(__NR_sched_setattr, 0, attr, 0);
}
static int wrapper_sched_set_runtime(int runtime)
{
struct sched_attr attr;
int ret = wrapper_sched_getattr(0, &attr, sizeof(attr), 0);
if (ret < 0)
{
perror("Unable to get scheduler attributes");
// throw scheduler_exception;
}
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = runtime;
return syscall(__NR_sched_setattr, 0, attr, 0);
}
static int wrapper_sched_yield()
{
return syscall(__NR_sched_yield);
}
cc version
$ /usr/bin/cc --version
cc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Why would the .text section of my object file be empty?
Because all your functions are static, they are optimized out.
undefined reference to `wrapper_sched_yield'
If you want the function to be externally visible, remove static. Also note extern "C" needed between C <-> C++ interoperability.
warning: implicit declaration of function ‘perror’
You did not include stdio.h
warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘const ConstPtr’ {aka ‘const boost::shared_ptr<const beginner_tutorials::RelativePosition_std::allocator<void > >’}
No idea what you are expecting here to happen. The code is invalid, you can't printf a boost::shared_ptr.

Why function pointer in the structure causes relocation at program load-time

I've the following code that I compile and run on Linux:
#include <stdio.h>
// can't be static
void do_stuff(void) {
;;
}
typedef void (*func)(void);
struct data {
func ptr;
};
static const struct data d = {
.ptr = do_stuff
};
int main() {
d.ptr();
}
If I compile it:
gcc -O0 -o main main.c
then d goes to .data.rel.ro segment.
objdump -t main | grep ".data.rel.ro"
0000000000003df8 l d .data.rel.ro 0000000000000000 .data.rel.ro
0000000000003df8 l O .data.rel.ro 0000000000000008 d
I understand that reason for it is a relocation done by loader and that's because of function pointer in the "data" structure.
What I don't get is - why it is done like that? What is a reason for having relocation here?

Objcopy symbols are mixed or invalid in executable

As a simple example of my problem, let's say we have two data arrays to embed into an executable to be used in a C program: chars and shorts. These data arrays are stored on disk as chars.raw and shorts.raw.
Using objcopy I can create object files that contain the data.
objcopy --input binary --output elf64-x86-64 chars.raw char_data.o
objcopy --input binary --output elf64-x86-64 shorts.raw short_data.o
objdump shows that the data is correctly stored and exported as _binary_chars_raw_start, end, and size.
$ objdump -x char_data.o
char_data.o: file format elf64-x86-64
char_data.o
architecture: i386:x86-64, flags 0x00000010:
HAS_SYMS
start address 0x0000000000000000
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 0000000e 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 g .data 0000000000000000 _binary_chars_raw_start
000000000000000e g .data 0000000000000000 _binary_chars_raw_end
000000000000000e g *ABS* 0000000000000000 _binary_chars_raw_size
(Similar output for short_data.o)
However, when I link these object files with my code into an executable, I run into problems. For example:
#include <stdio.h>
extern char _binary_chars_raw_start[];
extern char _binary_chars_raw_end[];
extern int _binary_chars_raw_size;
extern short _binary_shorts_raw_start[];
extern short _binary_shorts_raw_end[];
extern int _binary_shorts_raw_size;
int main(int argc, char **argv) {
printf("%ld == %ld\n", _binary_chars_raw_end - _binary_chars_raw_start, _binary_chars_raw_size / sizeof(char));
printf("%ld == %ld\n", _binary_shorts_raw_end - _binary_shorts_raw_start, _binary_shorts_raw_size / sizeof(short));
}
(compiled with gcc main.c char_data.o short_data.o -o main) prints
14 == 196608
7 == 98304
on my computer. The size _binary_chars_raw_size (and short) is not correct and I don't know why.
Similarly, if the _starts or _ends are used to initialize anything, then they may not even be located near each other in the executable (_end - _start is not equal to the size, and may even be negative).
What am I doing wrong?
The lines:
extern char _binary_chars_raw_start[];
extern char _binary_chars_raw_end[];
extern int _binary_chars_raw_size;
extern short _binary_shorts_raw_start[];
extern short _binary_shorts_raw_end[];
extern int _binary_shorts_raw_size;
They are not variables themselves. They are variables that are placed themselves at the beginning and end of the region. So the addresses of these variables are the start and end of the region. Do:
#include <stdio.h>
extern char _binary_chars_raw_start;
extern char _binary_chars_raw_end;
extern char _binary_chars_raw_size;
// print ptrdiff_t with %td
printf("%td == %d\n",
// the __difference in addresses__ of these variables
&_binary_chars_raw_end - &_binary_chars_raw_start,
(int)&_binary_chars_raw_size);
// note: alsoo print size_t like result of `sizeof(..)` with %zu
#edit _size is also a pointer

Why does the gold linker cause dl_iterate_phdr() not to return my custom note section?

On Linux, I would like to store some structures in a custom .note.foobar section and discover them at runtime.
I compile and link the program below once with gold and once without:
$ gcc -o test-ld test.c
$ gcc -o test-gold -fuse-ld=gold test.c
You can see that the ld-linked version finds the section while the gold-linked version does not:
$ ./test-ld
note section at vaddr: 2c4
note section at vaddr: 2f0
found f00dface
note section at vaddr: 324
note section at vaddr: 7a8
note section at vaddr: 270
note section at vaddr: 1c8
$ ./test-gold
note section at vaddr: 254
note section at vaddr: 7a8
note section at vaddr: 270
note section at vaddr: 1c8
However, the section does exist in both binaries:
$ readelf -x .note.foobar test-ld
Hex dump of section '.note.foobar':
0x000002f0 04000000 14000000 67452301 666f6f00 ........gE#.foo.
0x00000300 cefa0df0 00000000 00000000 00000000 ................
0x00000310 04000000 14000000 67452301 666f6f00 ........gE#.foo.
0x00000320 efbeadde ....
$ readelf -x .note.foobar test-gold
Hex dump of section '.note.foobar':
0x00000280 04000000 14000000 67452301 666f6f00 ........gE#.foo.
0x00000290 cefa0df0 00000000 00000000 00000000 ................
0x000002a0 04000000 14000000 67452301 666f6f00 ........gE#.foo.
0x000002b0 efbeadde ....
So you would expect the test-gold program to report a section at vaddr 280, but it does not.
Why can dl_iterate_phdr not find this section, while readelf can, and what is gold doing differently to cause this?
#define _GNU_SOURCE
#include <link.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct {
unsigned int elf_namesize;
unsigned int elf_datasize;
unsigned int elf_type;
unsigned int elf_name;
unsigned int bar;
} foo_t;
const foo_t __attribute__((used,section(".note.foobar,\"a\"#"))) foo1 = {
4,
20,
0x01234567,
0x6f6f66,
0xf00dface,
};
const foo_t __attribute__((used,section(".note.foobar,\"a\"#"))) foo2 = {
4,
20,
0x01234567,
0x6f6f66,
0xdeadbeef,
};
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
for (int i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
if (phdr->p_type == PT_NOTE) {
foo_t *payload = (foo_t*)(info->dlpi_addr + phdr->p_vaddr);
printf("note section at vaddr: %lx\n", phdr->p_vaddr);
if (phdr->p_memsz >= sizeof(foo_t) && payload->elf_type == 0x01234567 && payload->elf_name == 0x6f6f66) {
printf("found %x\n", payload->bar);
}
}
}
return 0;
}
int
main(int argc, char *argv[])
{
dl_iterate_phdr(callback, NULL);
return 0;
}
This code:
foo_t *payload = (foo_t*)(info->dlpi_addr + phdr->p_vaddr);
assumes that your .note.foobar is the very first Elf...Note in the PT_NOTE segment, but you can't make that assumption -- the order of notes in PT_NOTE is not guaranteed; you need to iterate over all of them.
You can verify that there are multiple notes with readelf -n test-{ld,gold}.
It appears that GNU-ld emits a separate PT_NOTE for each .note* section, while Gold merges them all into a single PT_NOTE segment. Either behavior is perfectly fine as far as ELF standard is concerned, though GNU-ld is wasteful (there is no need to emit extra PT_NOTE program headers).
Here is what I get for your test program:
readelf -l test-ld | grep NOTE
NOTE 0x00000000000002c4 0x00000000004002c4 0x00000000004002c4
NOTE 0x00000000000002f0 0x00000000004002f0 0x00000000004002f0
NOTE 0x0000000000000324 0x0000000000400324 0x0000000000400324
readelf -l test-gold | grep NOTE
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
P.S.
Why does the gold linker cause dl_iterate_phdr() not to return my custom note section?
The direct answer is that dl_iterate_phdr doesn't deal with (or care) about sections. It iterates over segments, and assignment of sections to segments is up for linkers to perform as they see fit.

If a global variable is initialized to 0, will it go to BSS?

All the initialized global/static variables will go to initialized data section.
All the uninitialized global/static variables will go to uninitialed data section(BSS). The variables in BSS will get a value 0 during program load time.
If a global variable is explicitly initialized to zero (int myglobal = 0), where that variable will be stored?
Compiler is free to put such variable into bss as well as into data. For example, GCC has a special option controlling such behavior:
-fno-zero-initialized-in-bss
If the target supports a BSS section, GCC by default puts variables that are initialized to zero into BSS. This
can save space in the resulting code. This option turns off this
behavior because some programs explicitly rely on variables going to
the data section. E.g., so that the resulting executable can find the
beginning of that section and/or make assumptions based on that.
The default is -fzero-initialized-in-bss.
Tried with the following example (test.c file):
int put_me_somewhere = 0;
int main(int argc, char* argv[]) { return 0; }
Compiling with no options (implicitly -fzero-initialized-in-bss):
$ touch test.c && make test && objdump -x test | grep put_me_somewhere
cc test.c -o test
0000000000601028 g O .bss 0000000000000004 put_me_somewhere
Compiling with -fno-zero-initialized-in-bss option:
$ touch test.c && make test CFLAGS=-fno-zero-initialized-in-bss && objdump -x test | grep put_me_somewhere
cc -fno-zero-initialized-in-bss test.c -o test
0000000000601018 g O .data 0000000000000004 put_me_somewhere
It's easy enough to test for a specific compiler:
$ cat bss.c
int global_no_value;
int global_initialized = 0;
int main(int argc, char* argv[]) {
return 0;
}
$ make bss
cc bss.c -o bss
$ readelf -s bss | grep global_
32: 0000000000400420 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
40: 0000000000400570 0 FUNC LOCAL DEFAULT 13 __do_global_ctors_aux
55: 0000000000601028 4 OBJECT GLOBAL DEFAULT 25 global_initialized
60: 000000000060102c 4 OBJECT GLOBAL DEFAULT 25 global_no_value
We're looking for the location of 0000000000601028 and 000000000060102c:
$ readelf -S bss
There are 30 section headers, starting at offset 0x1170:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
...
[24] .data PROGBITS 0000000000601008 00001008
0000000000000010 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000601018 00001018
0000000000000018 0000000000000000 WA 0 0 8
It looks like both values are stored in the .bss section on my system: gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4).
The behavior is dependent upon the C implementation. It may end up in either .data or .bss, and to increase changes that it does not end up in .data taking redundant space up, it's better not to explicitly initialize it to 0, since it will be set to 0 anyway if the object is of static duration.

Resources