0

I can only provide an example for this for Raspberry Pi Pico - therefore ARM - but I hope this question can also be answered generally in terms of gcc and .elf files.

When programming for the Pico, the pico-sdk contains macros like bi_decl and bi_1pin_with_name, which allow for embedding of information in executables, which can then be read with a PC program called picotool. My question is: can I (somehow) relatively easily read this data with binutils tools (such as readelf, objdump, etc)?

Here is an example - a Pico project with some trivial code, consisting of:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.12)

include(pico_sdk_import.cmake)

project(test_bi)

pico_sdk_init()

add_executable(test_bi test_bi.c)

target_link_libraries(test_bi pico_stdlib)
pico_add_extra_outputs(test_bi)

test_bi.c

#include <stdint.h>
#include "pico/binary_info.h"

bi_decl(bi_1pin_with_name(PICO_DEFAULT_SPI_CSN_PIN, "MYPINNAME"));

int main(void) {
  while ( 1 ) {
    ;
  }
}

When I build this, I get a test_bi.elf binary executable - and when I inspect that executable with picotool on the PC, I get:

$ picotool info -a test_bi.elf
File test_bi.elf:

Program Information
 name:          test_bi
 binary start:  0x10000000
 binary end:    0x10005224

Fixed Pin Information
 17:  MYPINNAME

Build Information
 sdk version:       1.4.0
 pico_board:        pico
 boot2_name:        boot2_w25q080
 build date:        Oct 21 2022
 build attributes:  Debug

So this confirms, that the information entered as bi_decl(bi_1pin_with_name(PICO_DEFAULT_SPI_CSN_PIN, "MYPINNAME")) in code, ended up in the .elf executable. (picotool likely used this case in the visit function to do the reading).

However, I cannot see "MYPINNAME" in my attempts to use readelf:

$ arm-none-eabi-readelf -a -W -n test_bi.elf | grep MYPIN
$

... but I can see it with strings -- which, when called with -t x, also gives relative offset address:

$ arm-none-eabi-strings -t x test_bi.elf | grep MYPIN
   4dff  MYPINNAME

... but I don't really understand how I could further look up this address in the .elf - just mere grepping of readelf output does not seem to help much:

$ arm-none-eabi-readelf -a -W -n test_bi.elf | grep 4d
   259: 100044d8     0 NOTYPE  LOCAL  DEFAULT    3 $d
   260: 100044d8    19 OBJECT  LOCAL  DEFAULT    3 __func__.1
   267: 100014da     0 NOTYPE  LOCAL  DEFAULT    2 $t
   471: 100034d6     0 NOTYPE  LOCAL  DEFAULT    2 l12_1
   543: 10003a4d    68 FUNC    LOCAL  DEFAULT    2 stdio_stack_buffer_flush
   733: 100014db    30 FUNC    GLOBAL DEFAULT    2 recursive_mutex_init

It's likely the relative address 4dff would map to absolute address 10004dff, but it seems I can't use it for much: trying objdump with a relative address range returns three different sections, none of which contain the string - and with an absolute address range, nothing is returned:

$ arm-none-eabi-objdump -S -s --start-address 0x4df0 --stop-address 0x4e10 test_bi.elf

test_bi.elf:     file format elf32-littlearm

Contents of section .debug_info:
 4df0 32000038 390f0000 01b23f02 01000080  2..89.....?.....
 4e00 3200007a 3200003a 3f0d0000 01b31381  2..z2..:?.......
Contents of section .debug_line:
 4df0 01050516 051c034f 01050513 03f9002e  .......O........
 4e00 050b0601 2e050506 03290105 1c03a77f  .........)......
Contents of section .debug_loc:
 4df0 0e00103c 0f001001 00563e0f 0010780f  ...<.....V>...x.
 4e00 00100100 56000000 00000000 000001f2  ....V...........

$ arm-none-eabi-objdump -S -s --start-address 0x10004df0 --stop-address 0x10004e10 test_bi.elf

test_bi.elf:     file format elf32-littlearm


To get closer to this, I compiled the project with make VERBOSE=1, extracted the gcc compilation line, added -E to it so only preprocessor output is generated - and I arrived to this equivalent of the above .c file, where the bi_decl(bi_1pin_with_name(PICO_DEFAULT_SPI_CSN_PIN, "MYPINNAME")) statement is expanded:

test_pi_exp.c:

#include <stdint.h>

typedef unsigned int uint;

typedef struct __attribute__((__packed__)) _binary_info_core {
        uint16_t type;
        uint16_t tag;
} binary_info_core_t;

typedef struct __attribute__((__packed__)) _binary_info_pins_with_name {
    struct _binary_info_core core;
    uint32_t pin_mask;
    const char * label;
} binary_info_pins_with_name_t;

static const __attribute__((__unused__)) int _error_bi_is_missing_enclosing_decl_4=0;
static const struct _binary_info_pins_with_name __bi_4 = {
  .core = {
    .type = (9 + _error_bi_is_missing_enclosing_decl_4),
    .tag = ((((uint)'P'&0xffu)<<8u)|((uint)'R'&0xffu)),
  },
  .pin_mask = (1u << (17)),
  .label = ("MYPINNAME") };
static const __attribute__((__used__)) __attribute__((section(".binary_info.keep." "__bi_ptr4"))) struct _binary_info_core *__bi_ptr4 = &__bi_4.core;;

int main(void) {
  while ( 1 ) {
    ;
  }
}

We can compile this without the pico-sdk with arm-none-eabi-gcc -g --specs=nosys.specs test_bi_exp.c -o test_bi_exp.elf.

Anyways, now we can tell the bi_decl... statement ended up as symbols prefixed with __bi_, here __bi_4 and __bi_ptr4; since it's not obvious to me exactly which symbol ended up with the label text content of the bi_decl, I'd say that the content cannot be obtained directly from these symbols (and that is why I used "indirect reading" in the question title).

Let's see how our executables compare in terms of __bi_ symbols:

$ arm-none-eabi-readelf -a -W -n test_bi.elf | grep __bi
    49: 10004c24     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr22
    50: 100002b4    12 OBJECT  LOCAL  DEFAULT    2 __bi_22
    52: 10004c28     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr30
    53: 100002a8    12 OBJECT  LOCAL  DEFAULT    2 __bi_30
    55: 10004c2c     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr38
    56: 1000029c    12 OBJECT  LOCAL  DEFAULT    2 __bi_38
    58: 10004c30     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr44
    59: 10004bf4    12 OBJECT  LOCAL  DEFAULT    3 __bi_44
    61: 10004c34     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr50
    62: 10000290    12 OBJECT  LOCAL  DEFAULT    2 __bi_50
    64: 10004c38     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr75
    65: 10004c00    12 OBJECT  LOCAL  DEFAULT    3 __bi_75
    67: 10004c3c     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr81
    68: 10004c0c    12 OBJECT  LOCAL  DEFAULT    3 __bi_81
    89: 10004c20     4 OBJECT  LOCAL  DEFAULT    4 __bi_ptr4
    90: 10003e0c    12 OBJECT  LOCAL  DEFAULT    3 __bi_4
   645: 10004c40     0 NOTYPE  GLOBAL DEFAULT    4 __binary_info_end
   744: 10004c20     0 NOTYPE  GLOBAL DEFAULT    4 __binary_info_start
   776: 100001e8     0 NOTYPE  GLOBAL DEFAULT    2 __binary_info_header_end

$ arm-none-eabi-readelf -a -W -n test_bi_exp.elf | grep __bi
  [10] .binary_info.keep.__bi_ptr4 PROGBITS        00018ac4 008ac4 000004 00  WA  0   0  4
   02     .init_array .fini_array .data .binary_info.keep.__bi_ptr4 .bss
    10: 00018ac4     0 SECTION LOCAL  DEFAULT   10 .binary_info.keep.__bi_ptr4
    60: 00008660    12 OBJECT  LOCAL  DEFAULT    4 __bi_4
    62: 00018ac4     4 OBJECT  LOCAL  DEFAULT   10 __bi_ptr4

The takeout here for me is:

  • the pico-sdk compiled .elf executable has several __bi_* entries (which correspond to other entries shown by picotool above); however there are no .binary_info.keep.__bi_* sections
  • the "plain" .elf executable still keeps .binary_info.keep.__bi_* sections, and has only the one __bi_4 entry that we specified in the file

Maybe if the pico-sdk .elf kept the .binary_info.keep.__bi_* sections things may have been easier - but given that they are not, let me restate what I want to do:

Even if I have to go through the extra gcc -E step, to find that a given bi_decl... statement ended as (a) given symbol(s) (say __bi_4) - could I use binutils tools to: start from symbol(s) name(s) like __bi_4; then obtain addresses that this/these symbol(s) "cover(s)"; and then obtain a binary hexdump of that address range, that would show (and confirm) that the text I used in source code (here "MYPINNAME"), did indeed end up at those locations? And if so - how do I that?

sdbbs
  • 4,270
  • 5
  • 32
  • 87
  • 1
    "I don't really understand how I could further look up this address in the .elf" -- does this answer https://stackoverflow.com/a/39335836/50617 give sufficient instructions? – Employed Russian Oct 22 '22 at 00:39

0 Answers0