Code snippets, ideas and events from IT related projects

by Robert Gawron

9 programów do debugowania i reverse engineeringu pod Linuksem

O co chodzi?

Dziś rozpiska niektórych narzędzi, pomocnych przy debugowaniu pod Linuksem. Niektóre z opisanych mają możliwość analizowania programu lub procesu, tu przypomnijmy: program to plik na dysku, proces to wykonujacy się program posiadający swój stan. Cytaty, jeśli nie podano inaczej pochodzą z manuali.

Trzeba nam kodu do testowania, użyjemy więc ciekawego kawałka mplayera. Oprogramowanie to jest na licencji GNU a materiały na tym blogu na CretiveCommons, więc IMHO kod wykorzystałem legalnie. Kompletny kod pobieramy z repozytorium (url i info jak to zrobić jest na stronie domowej programu), wykorzystamy te części, które odpowiadają za zebranie informacji o typie procesora (to irytujące info przy każdym odpaleniu). Do osobnego katalogu skopiujmy to, co będzie nam potrzebne:

  • cpudetect.h - struktury i funkcje z tego pliku wykorzytamy u siebie.
  • cpudetect.c
  • mp_msg.c
  • mp_msg.h
  • config.h - diabli wiedzą co tu siedzi :)
  • cpudetect.d
  • cputable.h
  • mp_msg.d

Będziemy kompilować z różnymi flagami, więc nie piszemy Makefile. Trzeba trochę własnego kodu, by zobaczyć czy to działa i ew. dodać swoje rzeczy, tworzymy więc plik moj_program.c a w nim:

   1  #include <stdio.h>
   2  #include "cpudetect.h"
   3  
   4  int main(){
   5      CpuCaps pr;
   6      GetCpuCaps(&pr);
   7      printf("cpuType: %s\ncpuStepping: %d\nhasMMX: \
   8  %d\nhasMMX2: %d\nhas3DNow: %d\nhas3DNowExt: \
   9  %d\nhasSSE: %d\nhasSSE2:  %d\nisX86: %d\n", pr.cpuType,
  10          pr.cpuStepping, pr.hasMMX, pr.hasMMX2, pr.has3DNow, 
  11          pr.has3DNowExt, pr.hasSSE, pr.hasSSE2, pr.isX86 );
  12  }
kompilujemy:
cc moj_program.c cpudetect.c mp_msg.c
efekt:
robert@robert855:~/mplayer.cpu$ ./a.out 
Segmentation fault (core dumped)
coś poszło nie tak..

gdb - debuger czyli analiza programu/procesu krok po kroku

Program się kompiluje ale wywala się w czasie działania lub nie działa poprawnie? Debuger pozwala nam analizować jak przebiega program (do jakich if'ów wchodzi, jakie funkcje wywołuje, jakie sa wartości zmiennych) krok po kroku, byśmy mogli znaleźć miejsce w kodzie i warunki przy których coś sie sypie. Dla programów w C skompilowanie programu z flagą g spowoduje czytelniejsze wyświetlanie informacji. Do działa:

robert@robert855:~/mplayer.cpu$ cc -g moj_program.c cpudetect.c mp_msg.c

Ustawimy breakpointa (miejsce, gdzie debuger zatrzyma proces i spyta co dalej) na sam początek, czyli maina. Opcja r uruchamia program, n i c służą do przechodzenia krok po kroku (w skrócie jedna przechodzi z analizą do środka funkcji a druga nie). Opcja p pozwala wyświetlić wartość zmiennej, [enter] powtarza ostatnie polecenie.

robert@robert855:~/mplayer.cpu$ gdb ./a.out 
GNU gdb 6.6-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) b main
Breakpoint 1 at 0x80488e8: file moj_program.c, line 6.
(gdb) r
Starting program: /home/robert/mplayer.cpu/a.out 

Breakpoint 1, main () at moj_program.c:6
6           GetCpuCaps(&pr);
(gdb) n
7           printf("cpuType: %s\ncpuStepping: %d\nhasMMX: \
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
0xb7e3bbfb in strlen () from /lib/tls/i686/cmov/libc.so.6

Widać, że coś jets nie tak z funkcją printf i z jej parametrami. Puszczenie gdb jeszcze raz i wyświetlenie co jest w pr pokaże nam, że wartości są raczej poprawne:

(gdb) n
7           printf("cpuType: %s\ncpuStepping: %d\nhasMMX: \
(gdb) p &pr
$5 = (CpuCaps *) 0xbfe01e54
(gdb) p pr
$6 = {cpuType = 15, cpuModel = 44, cpuStepping = 2, hasMMX = 1, hasMMX2 = 1, has3DNow = 1, has3DNowExt = 1, hasSSE = 1, 
  hasSSE2 = 1, isX86 = 1, cl_size = 64, hasAltiVec = 0, hasTSC = 1}

Po przyjżeniu się cpudetect.h widać, że pr.cpuType jest typu int a nie char*. Po poprawce z %s na %d i ponownej kompilacji kod działa poprawnie. Przykładowo dla mojego procesora:

cpuType: 15
cpuStepping: 2
hasMMX: 1
hasMMX2: 1
has3DNow: 1
has3DNowExt: 1
hasSSE: 1
hasSSE2:  1
isX86: 1

Dokładniejsze omówienie gdb można znaleźć na blogu Pontona.

gprof - analiza wydajności

Które fragmenty kodu wykonują sie najdłużej? Co trzeba zoptymalizować jeśli już zajdzie taka konieczność? Optymalizacja czego jest stratą czasu programisty?

Narzędzie umożliwia analizę programów pisanych w C, pascalu i fortranie, podczas ich kompilacji dodajemy flagę pg. Po pierwszym uruchomieniu skompilowanego programu utworzony zostanie pomocniczy plik gmon.out. Uruchomienie gprofa z opcją -b spowoduje niewyświetlanie legendy do tabelek - przydatne bo tekstu jest bardzo dużo.

The flat profile shows how much time your program spent in each function, and how many times that function was called. If you simply want to know which functions burn most of the cycles, it is stated concisely here.

The call graph shows, for each function, which functions called it, which other functions it called, and how many times. There is also an estimate of how much time was spent in the subroutines of each function. This can suggest places where you might try to eliminate function calls that use a lot of time.

robert@robert855:~/mplayer.cpu$ cc -pg moj_program.c cpudetect.c mp_msg.c
robert@robert855:~/mplayer.cpu$ ./a.out 
cpuType: 15
cpuStepping: 2
hasMMX: 1
hasMMX2: 1
has3DNow: 1
has3DNowExt: 1
hasSSE: 1
hasSSE2:  1
isX86: 1
robert@robert855:~/mplayer.cpu$ gprof -b ./a.out 
Flat profile:

Each sample counts as 0.01 seconds.
 no time accumulated

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  Ts/call  Ts/call  name    
  0.00      0.00     0.00        9     0.00     0.00  do_cpuid
  0.00      0.00     0.00        7     0.00     0.00  mp_msg
  0.00      0.00     0.00        7     0.00     0.00  mp_msg_test
  0.00      0.00     0.00        1     0.00     0.00  GetCpuCaps
  0.00      0.00     0.00        1     0.00     0.00  GetCpuFriendlyName
  0.00      0.00     0.00        1     0.00     0.00  check_os_katmai_support
  0.00      0.00     0.00        1     0.00     0.00  has_cpuid


                        Call graph


granularity: each sample hit covers 2 byte(s) no time propagated

index % time    self  children    called     name
                0.00    0.00       4/9           GetCpuFriendlyName [5]
                0.00    0.00       5/9           GetCpuCaps [4]
[1]      0.0    0.00    0.00       9         do_cpuid [1]
-----------------------------------------------
                0.00    0.00       1/7           check_os_katmai_support [6]
                0.00    0.00       6/7           GetCpuCaps [4]
[2]      0.0    0.00    0.00       7         mp_msg [2]
                0.00    0.00       7/7           mp_msg_test [3]
-----------------------------------------------
                0.00    0.00       7/7           mp_msg [2]
[3]      0.0    0.00    0.00       7         mp_msg_test [3]
-----------------------------------------------
                0.00    0.00       1/1           main [15]
[4]      0.0    0.00    0.00       1         GetCpuCaps [4]
                0.00    0.00       6/7           mp_msg [2]
                0.00    0.00       5/9           do_cpuid [1]
                0.00    0.00       1/1           has_cpuid [7]
                0.00    0.00       1/1           GetCpuFriendlyName [5]
                0.00    0.00       1/1           check_os_katmai_support [6]
-----------------------------------------------
                0.00    0.00       1/1           GetCpuCaps [4]
[5]      0.0    0.00    0.00       1         GetCpuFriendlyName [5]
                0.00    0.00       4/9           do_cpuid [1]
-----------------------------------------------
                0.00    0.00       1/1           GetCpuCaps [4]
[6]      0.0    0.00    0.00       1         check_os_katmai_support [6]
                0.00    0.00       1/7           mp_msg [2]
-----------------------------------------------
                0.00    0.00       1/1           GetCpuCaps [4]
[7]      0.0    0.00    0.00       1         has_cpuid [7]
-----------------------------------------------


Index by function name

   [4] GetCpuCaps              [1] do_cpuid                [3] mp_msg_test
   [5] GetCpuFriendlyName      [7] has_cpuid
   [6] check_os_katmai_support [2] mp_msg

ltrace - odwołania do bibliotek

Narzędzie pokaże nam odwołania do biblioteki, ich kolejność i argumenty.

robert@robert855:~/mplayer.cpu$ ltrace ./a.out 
__libc_start_main(0x8048a14, 1, 0xbff6d0c4, 0x80499e0, 0x80499d0 <unfinished ...>
__monstartup(0x8048920, 0x8049ad4, 0xbff6cff8, 0x80489b2, 0)                 = 0
__cxa_atexit(0x8048778, 0, 0, 0x804bae8, 0xbff6cfe8)                         = 0
mcount(0xb7fa2ce0, 0xb7f6fff4, 0x8048778, 0x80499e0, 0xbff6cfc0)             = 0xbff6d0c4
mcount(0x80483bc, 0xbff6cf80, 0xb7f95e52, 0xb7fa3898, 0xb7f88b38)            = 0xbff6cfe4
mcount(0xbff6cf98, 0xb7f0553f, 1, 0xbff6d040, 0xbff6cf98)                    = 0xbff6cfe4
mcount(0, 0, 0xbff6cfe4, 0x200212, 0xbff6cf98)                               = 0xbff6cf74
mcount(0, 0, 0, 0, 0)                                                        = 0xbff6cf78
mcount(0xbff6cf7c, 0xbff6cf38, 0x8049685, 32, 6)                             = 32
mcount(0, 0x69746e65, 1, 0xbff6cf7c, 0xbff6cf80)                             = 0xbff6cf64
mcount(0x80500a8, 0xbff6cf80, 0xb7fa2ce0, 0xbff6d018, 0xbff6cf38)            = 0xbff6cf74
malloc(256)                                                                  = 0x8050818
sprintf("AuthenticAMD", "%.4s%.4s%.4s", "AuthcAMDenti", "enti", "cAMDenti")  = 12
mcount(0xbff6d018, 0xbff6cee8, 0xb7e786ce, 0xbff6cf23, 0x804a6bb)            = 0xbff6cf74
mcount(0x80000000, 0x69746e65, 0x80000018, 0xbff6cf7c, 0xbff6cf80)           = 0xbff6cf74
strncat(0x8050818, 0xbff6cf74, 16, 0xbff6cf80, 0xbff6cf7c)                   = 0x8050818
mcount(0xb7fa2ce0, 0xbff6d018, 0xbff6cf38, 0xb7f9b300, 0x72706d65)           = 0xbff6cf74
strncat(0x8050818, 0xbff6cf74, 16, 0xbff6cf80, 0xbff6cf7c)                   = 0x8050818
mcount(0x80000003, 0x20202000, 0x74286e6f, 0xbff6cf7c, 0xbff6cf80)           = 0xbff6cf74
strncat(0x8050818, 0xbff6cf74, 16, 0xbff6cf80, 0xbff6cf7c)                   = 0x8050818
mcount(32, 6, 0, 0, 0)                                                       = 0x8050818
mcount(0x8050843, 0xbff6cf38, 0x8049685, 32, 4)                              = 32
free(0x8050818)                                                              = <void>
mcount(32, 4, 0, 0, 0)                                                       = 15
mcount(44, 0xbff6cf38, 0x8049685, 32, 4)                                     = 32
mcount(0xb7fa2ce0, 0x8050810, 0xbff6cf98, 0xb7f9b300, 0)                     = 0xbff6cf74
mcount(32, 4, 0, 0, 0)                                                       = 24
mcount(0xbff6cf7c, 0xbff6cf38, 0x8049685, 32, 6)                             = 32
mcount(0x80000000, 0x69746e65, 0x80000018, 0xbff6cf7c, 0xbff6cf80)           = 0xbff6cf64
mcount(0x80000001, 0xc3d3fbff, 135106, 0xbff6cf6c, 0xbff6cf70)               = 0xbff6cf64
mcount(32, 6, 0, 0, 0)                                                       = 0x1008140
mcount(0xbff6cf6c, 0xbff6cf38, 0x8049685, 32, 6)                             = 32
mcount(32, 6, 0, 0, 0)                                                       = 64
mcount(0xbff6cf6c, 0xbff6cf38, 0x8049685, 32, 6)                             = 32
mcount(0x804837c, 0xbff6ced0, 0xb7f04980, 0x8048ae2, 0x8050288)              = 1
sigaction(4, NULL, 0xbff6ceac)                                               = 0
signal(4, 0x8049274)                                                         = NULL
sigaction(4, 0xbff6ceac, NULL)                                               = 0
mcount(0, 0, 0, 0, 0)                                                        = 0
mcount(0xbff6cdf0, 0xbff6ce88, 0x8049685, 32, 6)                             = 32
printf("cpuType: %d\ncpuStepping: %d\nha"..., 15, 2cpuType: 15
cpuStepping: 2
hasMMX: 1
hasMMX2: 1
has3DNow: 1
has3DNowExt: 1
hasSSE: 1
hasSSE2:  1
isX86: 1
)                         = 106
_mcleanup(0, 106, 0xb7e5f8fb, 0xb7f6fff4, 0xb7fa2ce0)                        = 1
+++ exited (status 106) +++

strace - odwołania do systemu i sygnały

Program wykonywany jest przez system operacyjny (np. zapis/odczyt plików, dostęp do pamięci), interakcje z nim mogą nam trochę powiedzieć o jego zachowaniu programu.

robert@robert855:~/mplayer.cpu$ strace ./a.out 
execve("./a.out", ["./a.out"], [/* 29 vars */]) = 0
brk(0)                                  = 0x804f000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f1a000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=75107, ...}) = 0
mmap2(NULL, 75107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f07000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0`\1\000"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=1307104, ...}) = 0
mmap2(NULL, 1312164, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7dc6000
mmap2(0xb7f01000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13b) = 0xb7f01000
mmap2(0xb7f04000, 9636, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7f04000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7dc5000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7dc56c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7f01000, 4096, PROT_READ)   = 0
munmap(0xb7f07000, 75107)               = 0
brk(0)                                  = 0x804f000
brk(0x8071000)                          = 0x8071000
rt_sigaction(SIGPROF, {0xb7e97ab0, ~[], SA_RESTART}, {SIG_DFL}, 8) = 0
setitimer(ITIMER_PROF, {it_interval={0, 10000}, it_value={0, 10000}}, {it_interval={0, 0}, it_value={0, 0}}) = 0
rt_sigaction(SIGILL, NULL, {SIG_DFL}, 8) = 0
rt_sigaction(SIGILL, {0x8049274, [ILL], SA_RESTART}, {SIG_DFL}, 8) = 0
rt_sigaction(SIGILL, {SIG_DFL}, NULL, 8) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f19000
write(1, "cpuType: 15\n", 12cpuType: 15
)           = 12
write(1, "cpuStepping: 2\n", 15cpuStepping: 2
)        = 15
write(1, "hasMMX: 1\n", 10hasMMX: 1
)             = 10
write(1, "hasMMX2: 1\n", 11hasMMX2: 1
)            = 11
write(1, "has3DNow: 1\n", 12has3DNow: 1
)           = 12
write(1, "has3DNowExt: 1\n", 15has3DNowExt: 1
)        = 15
write(1, "hasSSE: 1\n", 10hasSSE: 1
)             = 10
write(1, "hasSSE2:  1\n", 12hasSSE2:  1
)           = 12
write(1, "isX86: 1\n", 9isX86: 1
)               = 9
setitimer(ITIMER_PROF, {it_interval={0, 0}, it_value={0, 0}}, NULL) = 0
rt_sigaction(SIGPROF, {SIG_DFL}, NULL, 8) = 0
open("gmon.out", O_WRONLY|O_CREAT|O_TRUNC|O_NOFOLLOW, 0666) = 3
write(3, "gmon\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20) = 20
writev(3, [{"\0", 1}, {" \211\4\10\324\232\4\10n\4\0\0d\0\0\0seconds\0\0\0\0\0"..., 32}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2268}], 3) = 2301
writev(3, [{"\1", 1}, {"8\212\4\10=\213\4\10\1\0\0\0", 12}, {"\1", 1}, {"h\213\4\10\263\212\4\10\1\0\0\0", 12}, {"\1", 1}, {"\240\213\4\10\342\212\4\10\1\0\0\0", 12}, {"\1", 1}, {"\350\213\4\0104\226\4\10\1\0\0\0", 12}, {"\1", 1}, {"\0\214\4\10\342\212\4\10\1\0\0\0", 12}, {"\1", 1}, {"\20\215\4\10\372\216\4\10\1\0\0\0", 12}, {"\1", 1}, {"H\215\4\0104\226\4\10\1\0\0\0", 12}, {"\1", 1}, {"\220\215\4\0104\226\4\10\1\0\0\0", 12}, {"\1", 1}, {"\240\215\4\10\342\212\4\10\1\0\0\0", 12}, {"\1", 1}, {"\330\215\4\0104\226\4\10\1\0\0\0", 12}, {"\1", 1}, {"\350\215\4\10\342\212\4\10\1\0\0\0", 12}, {"\1", 1}, {"h\216\4\10\342\212\4\10\1\0\0\0", 12}, {"\1", 1}, {"\220\216\4\0104\226\4\10\1\0\0\0", 12}, {"\1", 1}, {"\300\216\4\0104\226\4\10\1\0\0\0", 12}, {"\1", 1}, {"\320\216\4\10\276\222\4\10\1\0\0\0", 12}, {"\1", 1}, {"\220\217\4\10\342\212\4\10\1\0\0\0", 12}, ...], 38) = 247
close(3)                                = 0
brk(0x8070000)                          = 0x8070000
exit_group(106)                         = ?
Process 19321 detached

O strace można też przeczytać na blogu Marcina Gucia

valgrind - problemy z pamięcią

To narzędzie pozwala diagnozować problemy z pamięcią, np. wycieki, odwoływanie się do nie zaincjalizowanych zmiennych itp. Tutaj taka ciekawostka, otóż valgrind sam wykonuje kod emulując komputer, podobnie jak gdb, stąd info o rejestrach procesora jst inne.

valgrind: a.out: command not found
robert@robert855:~/mplayer.cpu$ valgrind ./a.out 
==19343== Memcheck, a memory error detector.
==19343== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==19343== Using LibVEX rev 1658, a library for dynamic binary translation.
==19343== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==19343== Using valgrind-3.2.1-Debian, a dynamic binary instrumentation framework.
==19343== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==19343== For more details, rerun with: -v
==19343== 
cpuType: 15
cpuStepping: 7
hasMMX: 1
hasMMX2: 1
has3DNow: 0
has3DNowExt: 0
hasSSE: 1
hasSSE2:  1
isX86: 1
==19343== 
==19343== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 11 from 1)
==19343== malloc/free: in use at exit: 6,154 bytes in 1 blocks.
==19343== malloc/free: 2 allocs, 1 frees, 6,410 bytes allocated.
==19343== For counts of detected errors, rerun with: -v
==19343== searching for pointers to 1 not-freed blocks.
==19343== checked 77,876 bytes.
==19343== 
==19343== LEAK SUMMARY:
==19343==    definitely lost: 0 bytes in 0 blocks.
==19343==      possibly lost: 0 bytes in 0 blocks.
==19343==    still reachable: 6,154 bytes in 1 blocks.
==19343==         suppressed: 0 bytes in 0 blocks.
==19343== Reachable blocks (those to which a pointer was found) are not shown.
==19343== To see them, rerun with: --show-reachable=yes
Profiling timer expired

By zobaczyć jak to działa można potestować kod dodając różne błędy związane z pamięcią, kompilując i sprawdzając jakie dostaniemy ostrzeżenia, przykładowo:

malloc(sizeof(1234000000));
int i;
printf("%d", i);
int i=31313123;
printf("%s", i);

Inne:

  • tcpflow, tcpdump, ss - interakcje z siecią, to już temat na inny post..
  • top - czy proces się zapetlił (zżera czas procesora) czy wysypał (zżera pamięć)? Jesli tak będzie u góry tabelki
  • lsof - czy proces zwolnił plik gdy skończył go używać?
  • tail z opcją f - przydatny do śledzenia logów.
  • flagi kompilatora - to też temat na inny post

Linki

gdb, gprof, strace, valgrind, ltrace, tcpflow, tcpdump

Pingbacks

No pingbacks yet

Comments

avatar
czarodziej , 15.06.2008 20:05, reply
z narzedzi typu strace/ltrace nie korzystam (bo w netbsd jest ktrace) wiec sie nie wypowiem, ale co do gdb... jak juz pokazujesz debugowanie programu ktory wywalil segmentation fault to lepiej wyciagac informacje z cora - bo w rzeczywistych warunkach nie zawsze potrafisz odtworzyc pad programu. Radze dopisac cos o komendach frame oraz info local :)
avatar
Robert , 15.06.2008 20:36, reply
No racja. Tych dwóch nie znałem, dzięki za info! jak się im przyjże to dopiszę.
avatar
czarodziej , 15.06.2008 21:02, reply
ogolnie to polecam przeczytanie info'a na temat gdb - ja tez z poczatku nie wiedzialem jak wielkie mozliwosci daje ten program.

Leave your reply

Let me know what you think

Required. 30 chars of fewer.

Required.

captcha image