Product SiteDocumentation Site

Глава 6. Отладка разработки

6.1. GDB
6.2. Удалённая отладка программ
6.3. Автонастройка hasher
При разработке не всегда есть возможность обратиться к исходникам программы для поиска неисправностей при работе. Часто при отладке получается довольствоваться лишь ассемблерным кодом. Для полноценной работы необходимы знания всех внутренностей исполняемых файлов и исходников программы, с которой нужно работать.
Структура бинарных файлов в Linux имеет особый формат ELF (Executable and Linkable Format). Он состоит из большого количества секций, описывающих связи компонентов программы, хранящихся в ней данных и обо всех именах объектов, присутствующих в коде. Информация о разделах, именах и связанных объектах доступна с помощью специальных утилит nm (описывает связи по статически собранным объектам) и readelf или objdump, позволяющих изучить в том числе и динамически собранные связи:
@builder: ex.c
#include <stdio.h>
int N = 42;

int fun2(int n) {
	return n*2+1;
}

int fun1(int c) {
	c += 1;
	return fun2(c) + fun2(c*2);
}

int main(int argc, char *argv[]) {
	int i;
	for(i=0; i <10; i++)
		printf("%d\n", fun1(N+i));
	return 0;
}
@builder
[builder@localhost ~]$ cc -O0 -g ex.c -o ex
[builder@localhost ~]$ nm ex
0000000000004004 D N
0000000000003dc8 d _DYNAMIC
0000000000003fb8 d _GLOBAL_OFFSET_TABLE_
0000000000002000 R _IO_stdin_used
		w _ITM_deregisterTMCloneTable
		w _ITM_registerTMCloneTable
0000000000002130 r __FRAME_END__
0000000000002008 r __GNU_EH_FRAME_HDR
0000000000004008 D __TMC_END__
000000000000039c r __abi_tag
0000000000004008 B __bss_start
		w __cxa_finalize@GLIBC_2.2.5
0000000000004000 D __data_start
00000000000010f0 t __do_global_dtors_aux
0000000000003db8 d __do_global_dtors_aux_fini_array_entry
0000000000003dc0 D __dso_handle
0000000000003db0 d __frame_dummy_init_array_entry
		w __gmon_start__
		U __libc_start_main@GLIBC_2.34
0000000000004008 D _edata
0000000000004010 B _end
00000000000011cc T _fini
0000000000001000 T _init
0000000000001050 T _start
0000000000004008 b completed.0
0000000000004000 W data_start
0000000000001080 t deregister_tm_clones
0000000000001130 t frame_dummy
000000000000114a T fun1
0000000000001139 T fun2
000000000000117a T main
		U printf@GLIBC_2.2.5
00000000000010b0 t register_tm_clones
[builder@localhost ~]$
[builder@localhost ~]$ readelf -h ex
ELF Header:
 Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
 Class:			     ELF64
 Data:			      2's complement, little endian
 Version:			   1 (current)
 OS/ABI:			    UNIX - System V
 ABI Version:			0
 Type:			      DYN (Position-Independent Executable file)
 Machine:			   Advanced Micro Devices X86-64
 Version:			   0x1
 Entry point address:		0x1050
 Start of program headers:	  64 (bytes into file)
 Start of section headers:	  17048 (bytes into file)
 Flags:			     0x0
 Size of this header:		64 (bytes)
 Size of program headers:	   56 (bytes)
 Number of program headers:	 13
 Size of section headers:	   64 (bytes)
 Number of section headers:	 39
 Section header string table index: 38
[builder@localhost ~]$
Для отладки программы необходимо сделать специальную подготовку исходных данных:
  1. Компиляция программы с нулевой оптимизацией (флаг -O0) для избежания несостыковок исходного текста программы с бинарным файлом (например, в исходнике есть переменная, которая нигде не используется. При оптимизации переменная будет удалена, а в исходниках её упоминание останется);
  2. Генерация полного debuginfo (флаг -g) для сбора данных, сгенерированных компилятором для описания исходного текста во время отладки;
  3. Доступ к исходникам, на которые будет осуществляться ссылка. Файлы с исходными текстами должны лежать там, где было указано во время сборки, по умолчанию — в текущем каталоге, однако в большинстве отладчиков есть возможность указать альтернативный каталог для поиска. Отлаживать возможно и без исходников, но только в формате ассемблерного кода.

6.1. GDB

Вообще, отладка — это огромная тема, подробнее этот аспект можно изучить в этой книжке. Рассмотрим более подробно отладчик GDB  — GNU Debugger.
GDB предоставляет достаточно удобный терминальный интерфейс для отладки программ. Основные команды отладчика:
  • run  — запуск программы для отладки;
  • set args  — установка параметров командной строки для отлаживаемой программы;
  • установка точек останова:
    • breakpoint  — срабатывает в момент выполнения отмеченной строки кода;
    • watchpoint  — срабатывает по изменению регистра или ячейки памяти;
    • catchpoint  — срабатывает по с++-исключению или системному вызову;
  • start  — создание временной точки останова в начале функции main() и запуск программы;
  • next  — следующий шаг программы (без перехода по функциям);
  • step  — следующий шаг программы (со всеми переходами в функции);
  • continue  — продолжение исполнения программы до следующей точки останова;
  • finish  — продолжение исполнения подпрограммы до выхода из неё;
  • list  — просмотр части программы, где сейчас остановился отладчик;
  • print  — просмотр значений переменных и выражений;
  • backtrace  — просмотр стека связанных фреймов текущего места обработки отладчика:
    • up / down  — перемещение «фокуса» отладчика по стеку фреймов;
  • display  — всё время показывает значение переменной;
  • dump  — запись в файл.
Для запуска GDB в hasher необходимо выдать права на монтирование системной директории /proc c правами на чтение и запись (причина этого описана тут). Корректное монтирование файловых систем происходит при одновременном выполнении следующих четырех условий:
  1. Файловая система описана в файле /etc/hasher-priv/fstab (причём для /proc в случае работы с GDB должны быть явно описаны права на чтение и запись):
    [root@VM ~]# cat /etc/hasher-priv/fstab
    # Information about mount points for the hasher-priv(8) helper program.
    # See fstab(5) for details.
    
    proc /proc proc rw,nosuid,nodev,noexec,gid=proc,hidepid=2 0 0
    [root@VM ~]#
    
  2. В конфигурации hasher-priv (/etc/hasher-priv/system) файловая система указана в опции allowed_mountpoints:
    [root@VM ~]# cat /etc/hasher-priv/system
    # Systemwide configuration for the hasher-priv(8) helper program.
    # See hasher-priv.conf(5) for details.
    
    allowed_mountpoints=/proc,/dev/pts,/dev/shm,/sys
    prefix=~:/tmp/.private
    [root@VM ~]#
    
    • при изменении вышеуказанных параметров необходимо перезапускать сервис hasher-privd:
      [root@VM ~]# systemctl restart hasher-privd.service
      [root@VM ~]#
      
  3. При запуске hasher файловая система должна быть указана в опции --mountpoints или, что то же самое, в ключе known_mountpoints конфигурационного файла hasher (~/.hasher/config):
    [user@VM ~]$ hsh-shell --mountpoints=/proc
    
  4. При сборке пакета файловая система должна быть указана сборочной зависимостью (например, BuildReq: /proc) собираемого пакета, прямой или косвенной (через зависимости сборочных зависимостей пакета).

Важно

Все примеры командной строки, содержащие подсказку «[root@…]#», требуют прав суперпользователя. Получив такие права, обращайтесь с системой осторожнее!
Запустим GDB с программой из примера выше и проследим изменение некоторых параметров:
@builder
[builder@localhost ~]$ gdb ex
GNU gdb (GDB) 14.1.0.56.d739d4fd457-alt1 (ALT Sisyphus)
<...>
Reading symbols from ex...
(gdb)
Запустим выполнение кода и будем отслеживать значения переменных при выполнении:
@builder
(gdb) run
Starting program: /usr/src/ex
<...>
Breakpoint 2, fun1 (c=42) at ex.c:9
9		c += 1;
(gdb) print c
$1 = 42
(gdb) next
10	      return fun2(c) + fun2(c*2);
(gdb) continue
Continuing.

Breakpoint 1, fun2 (n=43) at ex.c:5
5		return n*2+1;
(gdb) display n
1: n = 43
(gdb) continue
Continuing.

Breakpoint 1, fun2 (n=86) at ex.c:5
5		return n*2+1;
1: n = 86
(gdb)
При работе с исходными текстами программ большого размера удобно просматривать (не покидая GDB) блоки кода вокруг выполняемой строки:
@builder
(gdb) list
1	#include <stdio.h>
2	int N = 42;
3
4	int fun2(int n) {
5		return n*2+1;
6	}
7
8	int fun1(int c) {
9		c += 1;
10	      return fun2(c) + fun2(c*2);
(gdb)
@builder
(gdb) delete breakpoints
Delete all breakpoints? (y or n) y
(gdb) cont
Continuing.
260
266
272
278
284
290
296
302
308
314
[Inferior 1 (process 155112) exited normally]
(gdb) quit
[builder@localhost ~]$