رفتن به مطلب
مرجع رسمی سی‌پلاس‌پلاس ایران

برنامه نویسی

  • نوشته‌
    21
  • دیدگاه
    19
  • مشاهده
    9,517

مشارکت‌کنندگان این وبلاگ

عیب‌یابی، دیباگ‌کردن با GNU Debugger

قاسم رمضانی منش

1,572 بازدید


Image result for runtime error fun pic

بارها بوده که برنامه‌‌ای را نوشته‌ایم، روند کامپایل و اجرا به خوبی و خوشی انجام می‌شود. امّا در مرحلهٔ اجرای برنامه، خروجی‌های نامناسبی پدیدار می‌شود. که متأسفانه چیزی نیستند که ما می‌خواهیم. خب برای حل این مشکل دو راه پیش‌رو می‌باشد :

  • بازبینی کد و انجام تست برای یافتن محل مشکل.
  • استفاده از ابزارهای خطایابی (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 سورس برنامه‌ٔ شما بارگذاری می‌شود.

2019-05-21_22-13.thumb.png.50f113d47c64e7b3fed972e680c7776e.png

برویم به سراغ 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 متوقف شود :

2019-05-21_22-26.thumb.png.828b6f29565d27aa27b19c77181dd0a3.png

در این توقف، ما می‌توانیم با استفاده از دستور 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 دیدگاه


نظرهای پیشنهاد شده

هیچ دیدگاهی برای نمایش وجود دارد.

مهمان
افزودن دیدگاه

×   شما در حال چسباندن محتوایی با قالب بندی هستید.   حذف قالب بندی

  تنها استفاده از ۷۵ اموجی مجاز می باشد.

×   لینک شما به صورت اتوماتیک جای گذاری شد.   نمایش به عنوان یک لینک به جای

×   محتوای قبلی شما بازگردانی شد.   پاک کردن محتوای ویرایشگر

×   شما مستقیما نمی توانید تصویر خود را قرار دهید. یا آن را اینجا بارگذاری کنید یا از یک URL قرار دهید.

  • کاربران آنلاین در این صفحه   0 کاربر

    هیچ کاربر عضوی،در حال مشاهده این صفحه نیست.

×
×
  • جدید...