Linux中使用C語言載入data object 檔案資料
之前看別人的程式,看到作者在ROMFS實作時把資料轉成object檔案,然後在C語言中直接存取資料。他的作法是
- 寫一個host程式,將某個目錄下面的檔案和目錄轉成單一個binary
- 將這個binary轉成object檔案
- 更改link script,讓最後的binary可以存取object檔案的section
這個project使ARM的架構Realtime 作業系統。看完手癢也想看看Linux下面要怎麼做到類似的功能。本來想說是不是要動到link script,後來看到這邊的參考資料後,發現只要存取symbol就可以使用了。克服這個問題後就可以來寫個小程式驗證一下。
首先我們先弄個測試資料,很簡單就是一個數字顯示後面字串長度後再存放字串。 ```text my_data.txt 5Hello
接下來就是Makefile部份
```makefile Makefile
CFLAGS=-Wall -Werror -g
DATA_SRC=my_data.txt
DATA_OBJ=my_data.o
TARGET=test_obj
OBJS=$(patsubst %, %.o, $(TARGET)) $(DATA_OBJ)
BFN=elf64-x86-64
ARCH=i386
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS)
$(DATA_OBJ): $(DATA_SRC)
objcopy -I binary -O $(BFN) -B $(ARCH) --prefix-sections '.mydata' $^ $@
clean:
rm *.o *~ $(TARGET) -f
Makefile說明:
patsubst
- Makefile 提供的函數,用在pattern 替換,所以上面做了下列的代換
- test_obj轉成test_obj.o
- Makefile 提供的函數,用在pattern 替換,所以上面做了下列的代換
$@
- Makefile的內建巨集,代表target
$^
- Makefile的內建巨集,代表prerequisite
重點是把資料檔案轉成object的部份,節錄該部份並說明如下
objcopy -I binary -O $(BFN) -B $(ARCH) --prefix-sections '.mydata' $^ $@
-I binary
- 指定input檔案binary格式為binary
-O elf64-x86-64
- 指定output binary 格式為elf64-x86-64
-B i386
- 指定架構為i386
--prefix-sections '.mydata'
- 指定放在.mydata的section
至於要怎麼知道binary格式和架構呢?您可以用objcopy --info |less
配合file test_obj.o
找出平臺上的參數。
我們先來看一下section是不是真的放到.mydata? ```text objdump結果 $ objdump -h my_data.o
my_data.o: file format elf64-x86-64
Sections: Idx Name Size VMA LMA File off Algn 0 .mydata.data 00000007 0000000000000000 0000000000000000 00000040 2**0 CONTENTS, ALLOC, LOAD, DATA
我只知道.mydata的section,裡面的資訊目前還不清楚有什麼意義。
我們再用nm來看檔案內有哪些symbol
```text nm結果
$ nm my_data.o
0000000000000007 D _binary_my_data_txt_end
0000000000000007 A _binary_my_data_txt_size
0000000000000000 D _binary_my_data_txt_start
flag說明
D
- 資料放在初始的資料section
A
- Symbol的值已固定,之後link也不會更動。目前還不知道這樣透露出有什麼額外的資訊
接下來就是存取的方式了,測試程式如下。主要就是從section讀出字串再印出來。
#include <stdio.h>
#include <string.h>
#define MAX_CHARS (32)
extern char _binary_my_data_txt_start;
int main(int argc, char **argv)
{
char line[MAX_CHARS] = {0};
char *my_data_start_addr = (char *) &_binary_my_data_txt_start;
int str_len = (char) *my_data_start_addr - 0x30; /* 0x30 => '0' */
if (str_len > MAX_CHARS) {
str_len = MAX_CHARS;
}
strncpy(line, my_data_start_addr + 1, str_len);
printf("Read string is %s\n", line);
return 0;
}
程式說明如下
- 前面nm看到的symbol
_binary_my_data_txt_end
,_binary_my_data_txt_start
, 和_binary_my_data_txt_size
只是一個sybmol,這邊我們存的是字元所以宣告成char
- _binary_my_data_txt_start存放的是該section開始的資料,我們可以用gdb驗證一下
```text gdb 節錄 Breakpoint 1, main (argc=1, argv=0x7fffffffe1d8) at test_obj.c:10 10 char my_data_start_addr = (char ) &_binary_my_data_txt_start; (gdb) p _binary_my_data_txt_start $1 = 53 '5'
* 所以我們可以取得該資料位址後,讀出字串長度後,再複製後面的字串並列印出來。
最後執行結果如下
```text 執行結果
$ ./test_obj
Read string is Hello
參考資料
Linux中使用C語言載入data object 檔案資料 (續)
動機
前面討論了Linux中使用C語言載入data object 檔案資料,裡面提到資料轉成object,裡面會有三個symbol: _binary_objfile_start
, _binary_objfile_end
, _binary_objfile_size
。
Object symbol
man objcopy
找_size
可以看到
-B bfdarch
--binary-architecture=bfdarch
Useful when transforming a architecture-less input file into an object file. In this case the output architecture can be set to bfdarch. This
option will be ignored if the input file has a known bfdarch. You can access this binary data inside a program by referencing the special
symbols that are created by the conversion process. These symbols are called _binary_objfile_start, _binary_objfile_end and
_binary_objfile_size. e.g. you can transform a picture file into an object file and then access it in your code using these symbols.
測試: Segmentation fault版
簡單來說,和平臺無關的檔案轉成obj檔時,可以透過_binary_objfile_start
, _binary_objfile_end
, _binary_objfile_size
去存取資料。所以我再修改了一下測試程式,印出這些symbol 的內容
#include <stdio.h>
#include <string.h>
#define MAX_CHARS (32)
extern char _binary_my_data_txt_start;
extern char _binary_my_data_txt_end;
extern int _binary_my_data_txt_size;
int main(int argc, char **argv)
{
char line[MAX_CHARS] = {0};
char *my_data_start_addr = (char *) &_binary_my_data_txt_start;
int str_len = (char) *my_data_start_addr - 0x30; /* 0x30 => '0' */
if (str_len > MAX_CHARS) {
str_len = MAX_CHARS;
}
strncpy(line, my_data_start_addr + 1, str_len);
printf("_binary_my_data_txt_start: %c\n", _binary_my_data_txt_start);
printf("_binary_my_data_txt_end: %c\n", _binary_my_data_txt_end);
printf("_binary_my_data_txt_size: %d\n", _binary_my_data_txt_size);
printf("Read string is %s\n", line);
return 0;
}
而測試檔案為
5Hello
一跑起來會發生Segmentation fault如下
$ ./test_obj
_binary_my_data_txt_start: 5
_binary_my_data_txt_end:
Segmentation fault (core dumped)
分析
使用gdb可以看到當在存取_binary_my_data_txt_size
這行,我們還可以進一步來看這個變數
_binary_my_data_txt_start: 5
_binary_my_data_txt_end:
Program received signal SIGSEGV, Segmentation fault.
main (argc=1, argv=0x7fffffffe1d8) at test_obj.c:24
24 printf("_binary_my_data_txt_size: %d\n", _binary_my_data_txt_size);
(gdb) p _binary_my_data_txt_size
Cannot access memory at address 0x7
疑?變數位址是0x7?那我把測試檔案內容改一下,增加3個字元,再跑一次gdb。
8HelloABC
(gdb) p _binary_my_data_txt_size
Cannot access memory at address 0xa
可以看到位址從0x7
跑到0xa
,也就是10,這表示這個數字增加三了。這讓我懷疑這個symbol並不是一個變數,而是一個數值。因此我們可以將
printf("_binary_my_data_txt_size: %d\n", _binary_my_data_txt_size);
改成
printf("_binary_my_data_txt_size: %p\n", &_binary_my_data_txt_size);
另外一個有趣的地方是_binary_my_data_txt_end
並不是C
,而是\0
。而_binary_my_data_txt_end
前一個字元是\n
。使用ghex
去看my_data.txt可以看到最後一個資料其實是\n
。但是\0
怎麼出現的,目前還不清楚。
參考資料
man objcopy
- Linking raw data with C code