Visual c++ for linux development: практика использования для windows разработчиков

A crash course on compiling with gcc (or g++)

Gcc is the de facto compiler in Linux or any other *nix system. It also has Windows ports but on Windows, you’ll probably find the debugger in Visual Studio ‘easier’.

Suppose you have a file called main.cpp containing your c++ code. You should compile it with the following command:

g++ main.cpp -o main

While this will work fine and produce an executable file called main, you also need to put a -g flag to tell the compiler that you may want to debug your program later.

So the final command turns into:

g++ main.cpp -g -Wall -Werror -o main

(If you’re wondering what -Wall and -Werror are, you may find this page on GCC a useful read.)

Don’t worry if it looks cumbersome, you’ll get used to it!
(If you’ve got multiple source files you should use a good build system like make or Scons.)

Remote Debugging

For remote debugging, a remote stub should be implemented in the remote program that needs to be debugged. That means the kernel should contain the remote stub to talk to the host GDB during the debug session. This requires a kernel source change and it is a must if you are running your kernel on a test(real) machine and debugging from another machine. If you are using an emulator (Bochs or QEMU), you can use the GDB stub compiled into the emulator.

Implementing GDB Stub

To debug (using GDB) a kernel running on a real machine, the kernel needs to contain a GDB stub. For the i386 platform, GDB source includes a reference implementation of gdb-stub.c. It requires the following three functions from the kernel to read/write from a serial port and to set up the exception handlers.

   getDebugChar(); // to read a character from serial port
   putDebugChar(int); // to write a character to serial port
   exceptionHandler(int exception_number, void *exception_address); // to set exception handler

Implement the above functions in your kernel, include the gdb-stub.c in the compilation, and also call the following functions during kernel start to connect to the gdb host.

   InitSerialPort(sys_gdb_port, 9600, UART_DATA_BIT_8, UART_PARITY_NONE, UART_STOP_BIT_1);   /*set up the serial port*/
   set_debug_traps();   /*setup exception handlers*/
   kprintf("Waiting for GDB(0x%X) : ", sys_gdb_port );
   __asm__("int3");   /*break point exception to sync with GDB host*/

Now start your test machine and your kernel will wait for a GDB host connection.

Using Emulator Stubs

QEMU — GDB can be connected to QEMU by simply starting qemu with the -s -S command line switches.

Bochs — For GDB to be able to interface with Bochs, Bochs will need to be configured with the --enable-gdb-stub argument.
The bochssrc (or whatever configuration script is being used) will need to have the gdbstub line set to something like gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0.

Пример использования

GNU gdb 6.5
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-slackware-linux".
(gdb) run
Starting program: /home/sam/programming/crash
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0xc11000
This program will demonstrate gdb

Program received signal SIGSEGV, Segmentation fault.
0x08048428 in function_2 (x=24) at crash.c:22
22         return *y;
(gdb) edit
(gdb) shell gcc crash.c -o crash -gstabs+
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
warning: cannot close "shared object read from target memory": File in wrong format
`/home/sam/programming/crash' has changed; re-reading symbols.
Starting program: /home/sam/programming/crash
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0xa3e000
This program will demonstrate gdb
24
Program exited normally.
(gdb) quit

После того, как причина ошибки сегментации найдена, программа отредактирована, ошибка исправлена. Исправленная программа повторно собрана с GCC и запущена.

The Basics of GDB

Provided you’ve compiled your program with the debugging symbols enabled, you’re ready to start debugging. Any time there is text you should replace, I’ve put it in <angle brackets>.

gdb <executable name>
gdb main

Setting Breakpoints

You’ll probably want you program to stop at some point so that you can review the condition of your program. The line at which you want the program to temporarily stop is called the breakpoint.

break <source code line number>

Looking at the code

When the program is stopped, you can do a number of important things, but most importantly you need to see which part of the code you’ve stopped. The command for this purpose is «list». It shows you the neighbouring 10 lines of code.

Next and Step

Just starting and stopping isn’t much of a control. GDB also lets you to run the program line-by-line by the commands ‘next’ and ‘step’. There is a little difference between the two, though. Next keeps the control strictly in the current scope whereas step follows the execution through function calls.

Look at this example carefully;

Suppose you have a line in the code like

value=display();
readinput();

If you use the next command, the line (and the function, provided there aren’t breakpoints in it) gets executed and the control advances to the next line, readinput(), where you can perhaps examine ‘value’ to get an idea of how display() worked.

But if you use the step command, you get to follow what display() does directly, and the control advances to the first line of display(), wherever it is.

Examining your Variables

When you want to find the misbehaving portion of your program, it often helps to examine local variables to see if anything unexpected has occurred.
To examine a variable, just use

print <var name to print>

Note: You can also modify variables’ values by

set <var> = <value>

You can modify variables to see if an issue is resolved if the variable has another value or to force the program to follow a particular path to see if the reason for a bug was due to a variable having the wrong value.

Setting Watchpoints

Setting watchpoints is like asking the debugger to provide you with a running commentary of any changes that happen to the variables. Whenever a change occurs, the program pauses and provides you with the details of the change.

The command to set a simple watchpoint (a write watchpoint, i.e you are notified when the value is written) is

watch <var>

Here’s some example output when GDB pauses due to a change in <var>:

Continuing.
Hardware watchpoint 2: variable

Old value = 0
New value = 1
0x08048754 at main.cpp:31
31        variable=isalpha(ch)

Note: You can only set watchpoints for a variable when it is in scope. So, to watch something within another function or a inner block, first set a breakpoint inside that scope and then when the program pauses there, set the watchpoint.

Команды, определяемые пользователем

Команда, определяемая пользователем—это последовательность команд
GDB, которой вы назначаете имя, как новой команде. Это
осуществляется командой . Пользовательские команды могут иметь
до 10 параметров, разделенных пробелами. Внутри команды пользователя,
доступ к параметрам производится посредством $arg0…$arg9.
Вот простой пример:

define adder
  print $arg0 + $arg1 + $arg2

Для выполнения команды используйте:

adder 1 2 3

Этот пример определяет команду , которая печатает сумму трех
своих параметров

Обратите внимание, что параметры являются текстовыми
подстановками, так что они могут ссылаться на переменные, использовать
сложные выражения или даже выполнять вызовы подчиненных функций

Определить команду с именем имя-команды. Если команда
с таким именем уже имеется, у вас запрашивается подтверждение на ее
переопределение.

Определение команды состоит из других командных строк GDB,
которые задаются после команды . Конец этих команд
отмечается стpокой, содержащей .

Имеет один аргумент—вычисляемое выражение. Команда сопровождается
последовательностью команд, которые выполняются, если выражение истинно
(отлично от нуля). Затем может следовать необязательная стpока
, сопровождаемая последовательностью команд, которые
выполняются только при ложном значении данного выражения. Конец списка
отмечается стpокой, содержащей .

Синтаксис подобен : команда имеет один параметр, который
является вычисляемым выражением и должен сопровождаться командами,
по одной в стpоке, которые завершаются . Выполнение команд
повторяется, пока выражение истинно.

Описывает определенную пользователем команду имя-команды, так,
чтобы к ней можно было обращаться посредством . Команда
имя-команды должна быть определена ранее. Эта команда
считывает стpоки документации точно так же, как
считывает стpоки определения команды, до строки . После
завершения команды , написанная вами документация будет
отображаться командой для команды имя-команды.

Вы можете использовать команду неоднократно, чтобы
изменить документацию команды. Переопределение команды посредством
не изменяет документации.

Перечислить все определенные пользователем команды, вместе с первой
строкой документации (если она есть).

Вывести команды GDB, использовавшиеся для определения
имя-команды (но не ее документацию). Если имя-команды не
задано, вывести содержимое всех определенных пользователем команд.

При выполнении команд, определенных пользователем, команды
определения не печатаются. Ошибка в любой из них останавливает
выполнение всей определенной пользователем команды.

При использовании в интерактивном режиме, команды, обычно запрашивающие
подтверждение, выполняются без запроса, если они используется внутри
определенной пользователем команды. Многие команды GDB,
которые обычно печатают сообщения о своих действиях, опускают
их при использовании в команде, определенной пользователем.

An Example Debugging Session

The given code computes the factorial of a number erroneously. The goal of the debugging session is to pinpoint the reason of the error.

     #include<iostream>
     
     using namespace std;
     
     long factorial(int n);
     
     int main()
     {
         int n(0);
         cin>>n;
         long val=factorial(n);
         cout<<val;
         cin.get();
         return 0;
     }
     
     long factorial(int n)
     {
         long result(1);
         while(n--)
         {
             result*=n;
         }
         return result;
     }

Into the Debugger

Now follow the commands and the outputs carefully, especially the watchpoints. What I’m doing is basically:

  • Setting a breakpoint just in the line of the function call
  • Stepping into the function from that line
  • Setting watchpoints for both the result of the calculation and the input number as it changes.
  • Finally, analyzing the results from the watchpoints to find problematic behaviour
1.      $ g++ main.cpp -g -Wall -o main
2.      $ gdb main
3.      GNU gdb (GDB) Fedora (7.3-41.fc15)
4.      Copyright (C) 2011 Free Software Foundation, Inc.
5.      This GDB was configured as "i686-redhat-linux-gnu".
6.      For bug reporting instructions, please see:
7.      <http://www.gnu.org/software/gdb/bugs/>...
8.      Reading symbols from /home/manasij7479/Documents/main...done.
9.      (gdb) break 11
10.     Breakpoint 1 at 0x80485f9: file main.cpp, line 11.
11.     (gdb) run
12.     Starting program: /home/manasij7479/Documents/main
13.     3
14.     
15.     Breakpoint 1, main () at main.cpp:11
16.     11        long val=factorial(n);
17.     (gdb) step
18.     factorial (n=3) at main.cpp:19
19.     19        long result(1);
20.     (gdb) list
21.     14        return 0;
22.     15      }
23.     16
24.     17      long factorial(int n)
25.     18      {
26.     19        long result(1);
27.     20        while(n--)
28.     21        {
29.     22          result*=n;
30.     23        }
31.     (gdb) watch n
32.     Hardware watchpoint 2: n
33.     (gdb) watch result
34.     Hardware watchpoint 3: result
35.     (gdb) continue
36.     Continuing.
37.     Hardware watchpoint 3: result
38.     
39.     Old value = 0
40.     New value = 1
41.     factorial (n=3) at main.cpp:20
42.     20        while(n--)
43.     (gdb)
44.     Continuing.
45.     Hardware watchpoint 2: n
46.     
47.     Old value = 3
48.     New value = 2
49.     0x08048654 in factorial (n=2) at main.cpp:20
50.     20        while(n--)
51.     (gdb)
52.     Continuing.
53.     Hardware watchpoint 3: result
54.     
55.     Old value = 1
56.     New value = 2
57.     factorial (n=2) at main.cpp:20
58.     20        while(n--)
59.     (gdb)
60.     Continuing.
61.     Hardware watchpoint 2: n
62.     
63.     Old value = 2
64.     New value = 1
65.     0x08048654 in factorial (n=1) at main.cpp:20
66.     20        while(n--)
67.     (gdb)
68.     Continuing.
69.     Hardware watchpoint 2: n
70.     
71.     Old value = 1
72.     New value = 0
73.     0x08048654 in factorial (n=0) at main.cpp:20
74.     20        while(n--)
75.     (gdb)
76.     Continuing.
77.     Hardware watchpoint 3: result
78.     
79.     Old value = 2
80.     New value = 0
81.     factorial (n=0) at main.cpp:20
82.     20        while(n--)
83.     (gdb)
84.     Continuing.
85.     Hardware watchpoint 2: n
86.     
87.     Old value = 0
88.     New value = -1
89.     0x08048654 in factorial (n=-1) at main.cpp:20
90.     20        while(n--)
91.     (gdb)
92.     Continuing.
93.     
94.     Watchpoint 2 deleted because the program has left the block in
95.     which its expression is valid.
96.     
97.     Watchpoint 3 deleted because the program has left the block in
98.     which its expression is valid.
99.     0x08048605 in main () at main.cpp:11
100.    11        long val=factorial(n);
101.    (gdb) print val
102.    $1 = 1293357044
103.    (gdb) next
104.    12        cout<<val;
105.    (gdb) continue
106.    Continuing.
107.    0
108.    (gdb) quit
  while(n>0) //doesn't let n reach 0
  {
    result*=n;
    n--;        //decrements only after the evaluation
  }

История

Первоначально написан Ричардом Столлманом в 1988 году. За основу был взят отладчик DBX, поставлявшийся с дистрибутивом BSD. С до  гг. проект поддерживался Джоном Джилмором, во время его работы в компании Cygnus Solutions. В настоящее время разработка координируется Управляющим комитетом GDB (GDB Steering Committee), назначенным Free Software Foundation.

История выпусков

  • 4 июня 2017: GDB 8.0
  • 28 августа 2015: GDB 7.10
  • 6 февраля 2014: GDB 7.7
  • 26 апреля 2013: GDB 7.6
  • 7 августа 2012: GDB 7.5
  • 26 апреля 2012: GDB 7.4.1
  • 24 января 2012: GDB 7.4
  • 26 июля 2011: GDB 7.3
  • 2 сентября 2010: GDB 7.2
  • 15 марта 2010: GDB 7.1
  • 6 октября 2009: GDB 7.0
  • 27 марта 2008: GDB 6.8
  • 3 октября 2003: GDB 6.0

OPTIONS

-help -h List all options, with brief explanations.
-symbols=file -s file Read symbol table from file file.
-write Enable writing into executable and core files..
-exec=file -e file Use file file as the executable file to execute when appropriate, and for examining pure data in conjunction with a core dump.
-se=file Read symbol table from file file and use it as the executable file.
-core=file -c file Use file file as a core dump to examine.
-command=file -x file Execute GDB commands from file file.
-directory=directory -d directory Add directory to the path to search for source files.
-nx -n Do not execute commands from any ‘.gdbinit’ initialization files.
Normally, the commands in these files are executed after all the command options and arguments have been processed.
-quiet -q »Quiet». Do not print the introductory and copyright messages. These messages are also suppressed in batch mode.
-batch Run in batch mode. Exit with status 0 after processing all the command files specified with ‘-x’
(and ‘.gdbinit’, if not inhibited). Exit with nonzero status if an error occurs in executing the GDB commands in the command files.
-cd=directory Run GDB using directory as its working directory, instead of the current directory.
-fullname -f Emacs sets this option when it runs GDB as a subprocess. It tells GDB to output the full file name and line number in a standard,
-b bps
Set the line speed (baud rate or bits per second) of any serial interface used by GDB for remote debugging.
-tty=device Run using device for your program’s standard input and output

Remote Debugging with GDB

If you are familiar with gdb and prefer to use the command line, you can use gdb for ARM. I’ll use a build machine using Debian Squeeze as an example.

Make sure you are using EmDebian pakages URL in /etc/apt/sources.list:

Then install gdb for ARM:

Shell

sudo apt-get update
sudo apt-get install gdb-arm-linux-gnueabi

1
2

sudo apt-getupdate

sudo apt-getinstall gdb-arm-linux-gnueabi

There may be an error during installation:

Shell

Unpacking gdb-arm-linux-gnueabi (from …/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb) …
dpkg: error processing /var/cache/apt/archives/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb (—unpack):
 trying to overwrite ‘/usr/share/gdb/syscalls/amd64-linux.xml’, which is also in package gdb 7.0.1-2+b1
configured to not write apport reports
                                      dpkg-deb: subprocess paste killed by signal (Broken pipe)
Errors were encountered while processing:
 /var/cache/apt/archives/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)

1
2
3
4
5
6
7
8

Unpacking gdb-arm-linux-gnueabi(from…/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb)…

dpkgerror processing/var/cache/apt/archives/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb(—unpack)

 trying tooverwrite’/usr/share/gdb/syscalls/amd64-linux.xml’,which isalso inpackagegdb7.0.1-2+b1

configured tonotwriteapport reports

                                     dpkg-debsubprocess pastekilled by signal(Broken pipe)

Errors were encountered whileprocessing

 /var/cache/apt/archives/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb

ESub-process/usr/bin/dpkg returned an error code(1)

In that case, you can force gdb installation as follows:

Shell

sudo dpkg -i —force-overwrite /var/cache/apt/archives/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb

1 sudo dpkg-i—force-overwrite/var/cache/apt/archives/gdb-arm-linux-gnueabi_7.0.1-2_i386.deb

then run arm-linux-gnueabi-gdb without argument, and connect to the target board:

Shell

target remote target_ip:target_port

1 target remote target_iptarget_port

where target_ip and target_ip are the IP address and port stipulated in gdbserver command line.

After that, you can use gdb to debug the program normally. Simply type cont to run the program. For other commands type help or consult gdb manpage.

Вывод строк исходного текста

Чтобы вывести строки файла с исходным текстом, используйте команду
(сокращенно ). По умолчанию выводятся десять строк.
Существует несколько способов определения того, какую часть файла вы хотите
вывести.

Здесь представлены наиболее употребительные формы команды :

Вывести строки, расположенные вокруг строки с номером номер-строки
в текущем исходном файле.

Вывести строки, расположенные вокруг начала функции функция.

Вывести еще определенное количество строк. Если последние выведенные
строки выводились с помощью команды , то выводятся
строки, следующие за последними выведенными; если, однако,
последней выведенной строкой была одиночная строка, выведенная
как часть отображения кадра стека (see section ),
то выводятся строки, расположенные вокруг нее.

Вывести строки, расположенные непосредственно перед последними
выведенными.

По умолчанию, для любой из этих форм команды GDB
выводит десять строк исходного текста. Вы можете изменить это
командой :

Установить количество выводимых командой строк в число
(если аргумент команды не задает явно какое-нибудь другое число).

Отобразить количество строк, выводимых по команде .

Повторение команды нажатием RET отбрасывает аргумент,
так что это эквивалентно вводу просто . Это полезнее, чем
вывод тех же самых строк снова. Исключение сделано для параметра
`-‘; этот параметр сохраняется при повторе команды, так что каждое
повторение приводит к перемещению вверх по исходному файлу.

Обычно команда ожидает от вас ноль, один или два
указателя строк. Указатели строк определяют строки
исходного текста; существует несколько способов их задания, но
результат всегда заключается в задании строки исходного текста. Вот
полное описание возможных параметров команды :

Вывести строки, расположенные вокруг строки, определяемой указ-стр.

Вывести строки с перв до посл. Оба параметра являются
указателями строк.

Вывести строки, расположенные перед посл.

Вывести строки, начиная с перв.

Вывести строки, расположенные сразу за последними выведенными.

Вывести строки, расположенные непосредственно перед последними выведенными.

Описано в предыдущей таблице.

Ниже перечислены способы указания одиночной строки исходного
текста—все виды указателей строк.

Определяет строку с номером номер из текущего исходного файла.
Если в качестве параметров к команде задано два указателя
строк, это относится к тому же исходному файлу, что и первый указатель
строки.

Указывает на строку, смещенную вперед на смещение строк
относительно последней выведенной строки. Когда используется в качестве
второго указателя строки для команды , имеющей два указателя,
задает строку, смещенную на смещение строк вниз относительно строки,
определенной первым указателем.

Указывает на строку, расположенную на смещение строк раньше
последней выведенной строки.

Задает строку номер из исходного файла имя-файла.

Определяет строку, с которой начинается тело функции функция.
Например, в Си это строка с открывающейся фигурной скобкой.

Определяет строку с открывающейся фигурной скобкой, с которой
начинается тело функции функция в файле имя-файла. Имя
файла необходимо лишь для того, чтобы избежать неоднозначности, когда в
различных исходных файлах есть одинаково названные функции.

Определяет строку, соответствующую адресу адрес программы.
адрес может быть любым выражением.
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *