Thử thực hiện 4 Stage khi Compile C bằng GCC
GCC biên dịch một file .c thành file chạy trong 4 Stage. Preprocessing (tiền xử lý), Assembly Code Compiling (diên dịch sang mã Assembly), Machine Code Compiling (biên dịch sang mã máy), Linking. Trong compile thông thường dạng $gcc -o HelloWorld ...
GCC biên dịch một file .c thành file chạy trong 4 Stage.
Preprocessing (tiền xử lý), Assembly Code Compiling (diên dịch sang mã Assembly), Machine Code Compiling (biên dịch sang mã máy), Linking.
Trong compile thông thường dạng
1 2 3 |
$gcc -o HelloWorld HelloWorld.c |
Với câu lệnh trên, ta sẽ không thấy kết quả của 3 Stage đầu tiên.
Để hiểu rõ hơn, trong giới hạn hiểu biết, mình sẽ thử thực hiện các Stage bằng tay xem liệu có thể tạo ra file chạy như câu lệnh compile trên hay không.
1. Thực hiện Stage 1 (Preprocessing)
Như trong bài trước, stage này sẽ lấy đầu vào là file .c và cho kết quả đầu ra là file .i (thông thường)
Đầu vào: HelloWorld.c
Đầu ra: HelloWorld.i
Câu lệnh thực hiện:
1 2 3 |
$gcc -E HelloWorld.c -o HelloWorld.i |
Hoặc bằng câu lệnh cpp:
1 2 3 |
$cpp HelloWorld.c -o HelloWorld.i |
File HelloWorld.i khá dài so với HelloWorld.c.
Về nội dung, file HelloWorld.i vẫn là một file source C như bao file Source Code C khác mà thôi, chứ hoàn toàn chưa chuyển sang dạng khác.
2. Thực hiện Stage 2 (Compiling to Assembly Code)
Stage này sẽ chuyển từ source C chứa trong các file HelloWorld.i sang Assembly Code, file HelloWorld.s:
Đầu vào: HelloWorld.i
Đầu ra: HelloWorld.s
Câu lệnh thực hiện:
1 2 3 |
$gcc -S HelloWorld.i -o HelloWorld.s |
3. Thực hiện Stage 3 (Compiling to Machine Code)
Bước này sẽ chuyển Assembly Code sang mã máy mà chương trình sẽ chạy:
Đầu vào: HelloWorld.s
Đầu ra:HelloWorld.o
Câu lệnh thực hiện:
1 2 3 |
$as HelloWorld.s -o HelloWorld.o |
as chính là Assembler, một trình biên dịch Assembly.
4. Thực hiện Stage 4 (Linking)
Linker trong Linux là ld hay tên đầy đủ là GNU Linker. Xem thêm ở man ld
- Lần 1: Ta thấy 3 stage ở trên thực hiện không mấy khó khăn gì.
Cùng dạng câu lệnh như thế mà thực hiện thì sao.
Đây là kết quả:
1 2 3 4 5 6 |
$ ld HelloWorld.o -o HelloWorld ld: warning: cannot find entry symbol _start; defaulting to 00000000004000b0 HelloWorld.o: In function `main': HelloWorld.c:(.text+0xa): undefined reference to `puts' |
Lần 2: Đọc một chút về Linker trong man page.
Có vẻ puts sinh ra từ printf đang được gọi trong source.
Và hiểu ra một chút rằng, chương trình C ta viết sử dụng hàm printf, hàm này không phải ta tự viết.
Người mới học vẫn gọi là hàm chuẩn, hầu như không quan tâm nó đến từ đâu.
Nhưng ta đang làm manual mà. Bản thân ngôn ngữ C không bao gồm một thư viện, hay hàm nào cả.
Vậy hàm printf lấy từ đâu, ta phải chỉ cho Linker biết. Đó là thư viện libc, chứa những hàm cơ bản mà chúng ta bảo là chuẩn cho Linux.
Tham khảo ví dụ trong man page,
ta thực hiện lại việc link bằng command:
1 2 3 4 |
$ ld -o HelloWorld /lib/crt0.o HelloWorld.o -lc ld: cannot find /lib/crt0.o: No such file or directory |
Lỗi trên do không có file.
Bỏ /lib/crt0.o đi thì sao:
1 2 3 4 |
$ ld -o HelloWorld HelloWorld.o -lc ld: warning: cannot find entry symbol _start; defaulting to 0000000000400260 |
Lỗi trên entry_point (địa chỉ hàm mà CPU sẽ nhảy vào đầu tiên để bắt đầu thực hiện chương trình) chưa được khai báo.
Sửa như sau:
1 2 3 |
$ld -o HelloWorld HelloWorld.o -lc --entry main |
Câu lệnh thành công, file HelloWorld được tạo ra.
Tuy nhiên, khi chạy thì:
1 2 3 4 |
./HelloWorld bash: ./HelloWorld: No such file or directory |
Lỗi trên là không có file nào như thế. WTF, lỗi gì lạ vậy, rõ ràng là có mà.
1 2 3 |
Lần 3: Sau một hồi hỏi thầy GG. Nguyên nhân có vẻ là do dynamic loader linker mà HelloWorld yêu cầu không có trong hệ thống. |
Kiểm tra như sau:
1 2 3 4 5 6 |
$ readelf -l HelloWorld|grep interpreter [Requesting program interpreter: /lib/ld64.so.1] $ ls /lib/ld64.so.1 ls: cannot access '/lib/ld64.so.1': No such file or directory |
Kiểm tra dynamic loader linker từ file kết quả được build “không manual”.
Tức là bằng câu lệnh:
1 2 3 4 5 |
$gcc -o HelloWorld_auto HelloWorld.c $ readelf -l HelloWorld_auto |grep interpreter [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] |
Ta thử “ép buộc” sử dụng dynamic loader linker hiện có thì sao.
1 2 3 4 5 |
$ /lib64/ld-linux-x86-64.so.2 ./HelloWorld Hello World Segmentation fault (core dumped) |
Chạy được nhưng bị Segmentation fault.
Để hiểu kĩ tại sao xảy ra lỗi này, chắc có vẻ mất thời gian.
Lần 4: Bắt chước câu lệnh biên dịch auto thì sao nhỉ? GCC đã thực hiện quá trình linking khi tạo ra file HelloWorld_auto ở trên như thế nào? Thật may, sau một hồi hỏi thầy GG. Ta có thể thấy được toàn bộ tham số của câu lệnh biên dịch ở trên bằng tham số -v vào câu lệnh biên dịch.
1 2 3 |
$gcc -v -o HelloWorld_auto HelloWorld.c |
Kết quả từ câu lệnh trên khá rắc rối, (có lẽ cần 1 bài khác để nói kĩ hơn về nó).
Tuy nhiên ta chỉ quan tâm đến đoạn tham số của collect2 (chính là Linker mà GCC sử dụng cho ngôn ngữ C) mà thôi.
Nó như thế này:
1 2 3 4 |
COLLECT_GCC_OPTIONS='-v' '-o' 'HelloWorld_auto' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccy1PInh.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o HelloWorld_auto /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. /tmp/ccocGwHc.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o |
Nhìn nản luôn, edit lại chút cho “rắc rối hơn”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
COLLECT_GCC_OPTIONS='-v' '-o' 'HelloWorld_auto' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccy1PInh.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass
Có thể bạn quan tâm
0
|