gdb عیبیابی، دیباگکردن با GNU Debugger
بارها بوده که برنامهای را نوشتهایم، روند کامپایل و اجرا به خوبی و خوشی انجام میشود. امّا در مرحلهٔ اجرای برنامه، خروجیهای نامناسبی پدیدار میشود. که متأسفانه چیزی نیستند که ما میخواهیم. خب برای حل این مشکل دو راه پیشرو میباشد :
- بازبینی کد و انجام تست برای یافتن محل مشکل.
- استفاده از ابزارهای خطایابی (Debugging).
بازبینی کد و انجام تست برای یافتن محل مشکل
برای اینکار فریورکها و کتابخانههای زیادی موجود میباشد، مثلاً کتابخانهٔ تستنویسی (Test Case) به اسم Catch2. کار با این کتاخانه بسیار آسان است. برای مثال تابع زیر را در نظر داشتهباشید :
unsigned int Factorial( unsigned int number ) {
return number <= 1 ? number : Factorial(number-1)*number;
}
ما میخواهیم بدانیم که آیا این تابع خروجی مناسب را دارد یا خیر، درصورتیکه از catch2
استفاده میکنید، کافیست که طبق راهنمای README.md
هدرفایل catch.cpp
را دریافت و به برنامهٔ خود اضافه کنید :
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"
unsigned int Factorial( unsigned int number ) {
return number <= 1 ? number : Factorial(number-1)*number;
}
TEST_CASE( "Factorials are computed", "[factorial]" ) {
REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
REQUIRE( Factorial(10) == 3628800 );
}
و بهصورت نمونهٔ کد بالا از catch2
استفاده میکنیم. طبق اسناد با تعریف ماکروی CATCH_CONFIG_MAIN
ما یک تابع main
توسط خود catch2
تعریف میکنیم. و کافیه که فقط این سورس را کامپایل و اجرا کنیم :
$> g++ -o catch2Test main.cpp
$>
$> ./catch2Test
===============================================================================
All tests passed (4 assertions in 1 test case)
و خب مسلماً درصورتیکه خطایی باشد در این آزمایشات معلوم میگردد، فریمورکهای دیگری مانند Google Test نیز موجود میباشد.
استفاده از ابزارهای خطایابی (Debugging)
در این روش شما برنامهٔ خود را تحت برنامهٔ دیگری اجرا و اقدام به خطایابی میکنید، یکی از برنامههای خطایابی معروف و آزاد، برنامهٔ GNU Debugger میباشد. از این برنامه برای خطایابی در زبانهای :
- Ada
- Assembly
- C
- ++C
- D
- Fortran
- Go
- Objective-C
- OpenCL
- Modula-2
- Pascal
- Rust
استفاده میشود.
برای نصب میتوانید از مدیربستهٔ سیستمعامل خود استفاده کنید :
$> apt install gdb gdb-doc
و یا اینکه از این پیوند برنامه را دریافت و اقدام به کامپایل/نصب آن کنید.
نکته : دقت کنید که همیشه نباید حتماً خطایی در برنامه باشد تا اقدام به خطایابی کنیم، گاهی هم نیاز است که روند کار برنامه را به این روش با استفاده از ابزارهای مشابه پیگیری کنیم. (In fact: reverse engineering)
حال اقدام به Debug کردن این برنامهٔ کوتاه مینماییم :
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
static int data = 0x02A;
int
main (void){
int stack = 0x2FF;
pid_t pid;
switch (pid = fork()){
case -1:
perror("pid = fork()");
exit(EXIT_FAILURE);
case 0:
data |= 0x02B;
stack &= data;
break;
}
fprintf(stdout, "[%s]\v [PID=%ld] [data=%d] [stack=%d].\n",
(pid == 0) ? "child" : "parent", (long) getpid(),
data, stack);
exit(EXIT_SUCCESS);
}
قبلاز اینکه برنامهای را برای دیباگ کردن داخل GDB استفاده کنیم، نیاز است که برای کمک به این روند اطلاعاتی مانند مکان Source Code برنامه را به فایل باینری خود اضافه کنیم. برای اینکار کافیست که از فلگ -g
یا -ggdb
یا -g3
استفاده کنیم. این پیوند را برای اطلاعات بیشتر مطالعه کنید.
به اینصورت برنامه را کامپایل و برای دیباگ کردن آماده میکنیم :
$> gcc -o output -ggdb main.c
توجه کنید که سورس برنامه را از مکانش تغییر ندهید. حال برنامهٔ gdb
را با نوشتن اسم gdb
در shell
فراخوانی میکنیم :
$> gdb
GNU gdb (Debian 8.2.1-2) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)
برای اینکه فایل باینری را باز کنیم باید از دستور file
استفاده کنیم. همچنین میتوانیم به اینصورت نیز برنامهٔ gdb
را به همراه فایل باینری خود اجرا نماییم :
$> gdb output
خب، در این قسمت میتوانیم با استفاده از دستور run
برنامهٔ خودمان را اجرا کنیم و درصورتیکه نیاز باشد آرگومانهایی را نیز ارسال کنیم. وقتی دستور run
را وارد میکنیم برنامهٔ ما اجرا میشود و خاتمه میابد. زمانیکه نیاز داریم روند اجرای برنامه در نقطههای مشخصی متوقف شود باید از Break Point استفاده کنیم. برای قرار دادن Break Point در خطهای مختلف برنامه از دستور break
به اضافهٔ شمارهٔ خط سورس برنامه استفاده میکنیم.
برای اینکار بد نیست که اطلاعاتی نیز درمورد سورسکد خود داشتهباشیم مثلاً همزمان بتوانیم سورس را نیز مشاهده کنیم، درصورتیکه از ادیتور Emacs
استفاده میکنید میتوانید M+x
را زده و gdb
را اجرا کنید. که یک بافر در سمت چپ برای شما باز میکند (درصورتیکه دکمههای Ctrl + X
و Ctrl + 3
را زده باشید). در غیر اینصورت به دو روش دیگر میتوانید سورس خود را هنگام دیباگ مشاهده کنید، در روش اوّل استفاده از دستور list
به اضافهٔ دو آرگومان میباشد :
-
آرگومان اوّل مشخص کنندهٔ خط شروع است، و آرگومان دوّم مشخص کننده خط پایان هست. مثلاً با فراخوانی
list 2, 10
از خط دو تا ده را نمایش میدهد :
(gdb) list 2, 10
2 #include <stdlib.h>
3 #include <sys/types.h>
4 #include <unistd.h>
5
6 static int data = 0x02A;
7
8 int
9 main (void){
10 int stack = 0x2FF;
-
میتوانید فقط یک آرگومان ارسال کنید، که به اندازه ی مقدار متغیر
listsize
که پیشفرض ده میباشد (میتوانید با استفاده از دستورset
مقدار را تغییر دهید) را نمایش میدهد :
(gdb) list 2
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/types.h>
4 #include <unistd.h>
5
6 static int data = 0x02A;
7
8 int
9 main (void){
10 int stack = 0x2FF;
حالات دیگهای هم دارد که میتوانید با وارد کردن help list
متوجه شوید.
امّا روش بهتری (روش دوّم) نیز برای دیدن سورس برنامه به همراه دیباگ کردن وجود دارد، میتوانید از رابط TUI
استفاده کنید. برای استفاده از این رابط دکمههای Ctrl + X
و Ctrl + A
یا <
وارد کرده و ReturnKey
(یا همان Enter
) را بزنید. در این قسمت شما به راحتی میتوانید هم سورس برنامهٔ خودتان را ببینید و هم برنامه را دیباگ کنید. با یکبار اجرا کردن برنامه توسط دستور run
سورس برنامهٔ شما بارگذاری میشود.
برویم به سراغ Break Point گذاشتن، برای مثال ما میخواهیم یک Break Point بر سر خط ۱۰ و ۱۳ بگذاریم :
(gdb) break 11
Breakpoint 1 at 0x555555555185: file main.c, line 13.
(gdb) break 10
Breakpoint 2 at 0x55555555517e: file main.c, line 10.
(gdb)
دوباره برنامه را با وارد کردن دستور run
اجرا میکنیم، تا اجرای برنامه اینبار در برخورد با اوّلین Break Point متوقف شود :
در این توقف، ما میتوانیم با استفاده از دستور print
محتویات متغیرهارا مشاهده کنیم :
(gdb) print data
$1 = 42
(gdb) print stack
$2 = 21845
دستور print
قابلیتهای جالبی دارد :
(gdb) print 12 / 2
$1 = 6
(gdb) print sizeof(int)
$2 = 4
(gdb) print &data
$3 = (int *) 0x4a60f0 <data>
(gdb)
مقادیری که به ترتیب در سمت چپ شمارهگذاری شدهاند درواقع اسم متغیرهایی هستنند که خروجی در آن قرار گرفته است :
(gdb) print $1
$4 = 42
(gdb) print $4
$5 = 42
(gdb)
همچنین با استفاده از دستور delete
میتوانیم یک Break Point را حذف کنیم. برای ادامه دادن به روند اجرای برنامه تا Break Point بعدی از دستور continue
و برای رفتن به خط بعدی از دستور next
استفاده میکنیم.بعد از اجرای دستور next
دقت کنید سریعاً به خط 23 رفته و فراخوانی تابع سیستمی fork()
را رها میکند. به خاطر اینکه دستور next
کاری به توابعی که فراخوانی کردهاید، در اینجا فراخوان سیستمی fork()
، ندارد و دستورات سورس شما را ادامه میدهد؛ امّا درصورتیکه از step
یا stepi
استفاده بکنید وارد دستورات شده و دستورات توابع شما را پیمایش میکند:
(gdb) next
[child]
[PID=563] [data=43] [stack=43].
[Detaching after fork from child process 563]
(gdb)
نکته : رابط TUI
زیاد قوی نمیباشد، لذا درصورتیکه خروجی چاپ شود تنظیمات صفحه نمایش را به هم میزد میتوانید با زدن Ctrl + L
خروجیهای اضافه را از بین ببرید.
برای دیباگ کردن یک fork()
میتوانید مقدار follow-fork-mode را ویرایش کنید :
(gdb) set follow-fork-mode child
(gdb) run
Starting program: /tmp/output
Breakpoint 2, main () at main.c:10
(gdb) next
Breakpoint 1, main () at main.c:13
(gdb) step
[Attaching after process 2387 fork to child process 2403]
[New inferior 2 (process 2403)]
[Detaching after fork from parent process 2387]
[Inferior 1 (process 2387) detached]
[Switching to process 2403]
main () at main.c:13
(gdb)
همچنین با استفاده از دستور disassemble
میتوانید سورس اسمبلی یکی تابع را مشاهده کنید :
(gdb) disassmble main
Dump of assembler code for function main:
0x0000000000401b4d <+0>: push %rbp
0x0000000000401b4e <+1>: mov %rsp,%rbp
0x0000000000401b51 <+4>: push %rbx
0x0000000000401b52 <+5>: sub $0x18,%rsp
=> 0x0000000000401b56 <+9>: movl $0x2ff,-0x14(%rbp)
0x0000000000401b5d <+16>: callq 0x43c9b0 <fork>
0x0000000000401b62 <+21>: mov %eax,-0x18(%rbp)
0x0000000000401b65 <+24>: cmpl $0xffffffff,-0x18(%rbp)
0x0000000000401b69 <+28>: je 0x401b73 <main+38>
0x0000000000401b6b <+30>: cmpl $0x0,-0x18(%rbp)
0x0000000000401b6f <+34>: je 0x401b89 <main+60>
0x0000000000401b71 <+36>: jmp 0x401ba2 <main+85>
0x0000000000401b73 <+38>: lea 0x7c48e(%rip),%rdi # 0x47e008
0x0000000000401b7a <+45>: callq 0x408bd0 <perror>
0x0000000000401b7f <+50>: mov $0x1,%edi
0x0000000000401b84 <+55>: callq 0x408010 <exit>
0x0000000000401b89 <+60>: mov 0xa4561(%rip),%eax # 0x4a60f0 <da
--Type <RET> for more, q to quit, c to continue without paging--
لینک منبع را برای ادامهٔ داستان دنبال کنید :).
موفق و پیروز باشید.
- 1
0 دیدگاه
نظرهای پیشنهاد شده
هیچ دیدگاهی برای نمایش وجود دارد.