Русский
Русский
English
Статистика
Реклама

Gdb

Как Иван ошибку в бэкенде локализовывал

09.09.2020 12:15:30 | Автор: admin
В комментариях к одной из моих статей про базовые команды Linux shell для тестировщиков справедливо заметили, что в ней не было указано применение команд в процессе тестирования. Я подумал, что лучше поздно, чем никогда, поэтому решил рассказать историю Backend QA-инженера Вани, который столкнулся с неожиданным поведением сервиса и попытался разобраться, где именно случилась ошибка.



Что тестировал Ваня


Ваня знал, что ему предстоит тестировать связку nginx + сервис.
Здесь я сразу сделаю ремарку: такая связка была выбрана для этой статьи просто потому, что она нагляднее всего может продемонстрировать применение различных утилит при дебаге проблемы и потому, что её очень просто сконфигурировать и поднять. В реальных условиях это может быть либо просто сервис, либо цепочка сервисов, которые делают запросы друг другу.

В качестве сервиса выступает дефолтный HTTP сервер Python SimpleHTTPServer, который в ответ на запрос без параметров выводит содержимое текущей директории:

[root@ivan test_dir_srv]# ls -ltotal 0-rw-r--r-- 1 root root 0 Aug 25 11:23 test_file[root@ivan test_dir_srv]# python3 -m http.server --bind 127.0.0.1 8000Serving HTTP on 127.0.0.1 port 8000 (http://personeltest.ru/away/127.0.0.1:8000/) ...

Nginx же сконфигурирован следующим образом:

upstream test {server 127.0.0.1:8000;}server {listen    80;location / {proxy_pass http://test;}}

Ване нужно было протестировать один-единственный тест-кейс: проверить, что запрос на / работает. Он проверил, и всё работало:

MacBook-Pro-Ivan:~ ivantester$ curl http://12.34.56.78<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Directory listing for /</title></head><body><h1>Directory listing for /</h1><hr><ul><li><a href="test_file">test_file</a></li></ul><hr></body></html>

Но затем в один момент на тестовом стенде разработчики что-то обновили, и Ваня получил ошибку:

MacBook-Pro-Ivan:~ ivantester$ curl http://12.34.56.78<html><head><title>502 Bad Gateway</title></head><body bgcolor="white"><center><h1>502 Bad Gateway</h1></center><hr><center>nginx/1.14.2</center></body></html>

Он решил не скидывать эту непонятную ошибку разработчикам, а получить доступ по ssh на сервер и разобраться, в чём там дело. Знаний в области такого рода дебага проблем у него было мало, но он очень хотел научиться, поэтому вооружился поисковиками, логикой и пошёл локализовывать баг.

Первая мысль Вани: логи


Действительно, если случилась ошибка, то нужно просто найти её в лог-файле. Но сначала нужно найти сам лог-файл. Ваня полез в Google и узнал, что часто логи лежат в директории /var/log. Действительно, там нашлась директория nginx:

[root@ivan ~]# ls /var/log/nginx/access.log access.log-20200831 error.log error.log-20200831

Иван посмотрел последние строчки error лога и понял, в чём дело: разработчики ошиблись в конфигурации nginx, в порт upstream закралась опечатка.

[root@ivan ~]# tail /var/log/nginx/error.log2020/08/31 04:36:21 [error] 15050#15050: *90452 connect() failed (111: Connection refused) while connecting to upstream, client: 31.170.95.221, server: , request: "GET / HTTP/1.0", upstream: "http://127.0.0.1:8009/", host: "12.34.56.78"

Какой можно сделать из этого вывод? Логи лучший друг тестировщиков и разработчиков при локализации ошибок. Если есть какое-то неожиданное поведение сервиса, а в логах при этом ничего нет, то это повод вернуть задачу в разработку с просьбой добавить логов. Ведь если б nginx не писал в лог о неудачной попытке достучаться до апстрима, то, согласитесь, искать проблему было бы сложнее?

В тот момент Ваня задумался: А что, если бы в nginx логи лежали в другой директории? Как бы я их нашёл? Через пару лет у Вани будет больше опыта работы с сервисами в Linux, и он будет знать, что путь к лог-файлу часто передают сервису аргументом командной строки, либо он содержится в файле конфигурации, путь к которому также часто передают сервису аргументом командной строки. Ну и в идеале путь к лог-файлу должен быть прописан в документации сервиса.

Кстати, через файл конфигурации можно найти путь к лог-файлу и в nginx:

[root@ivan ~]# ps ax | grep nginx | grep masterroot   19899 0.0 0.0 57392 2872 ?    Ss  2019  0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf[root@ivan ~]# grep "log" /etc/nginx/nginx.conferror_log /var/log/nginx/error.log warn;log_format main '$remote_addr - $remote_user [$time_local] "$request" 'access_log /var/log/nginx/access.log main;

А что если в логах ничего нет?


В свободное время Ваня решил подумать, а как бы он справился с задачей, если бы nginx не писал ничего в лог. Ваня знал, что сервис слушает порт 8000, поэтому решил посмотреть трафик на этом порту на сервере. С этим ему помогла утилита tcpdump. При правильной конфигурации он видел запрос и ответ:

Дамп трафика на порту 8000
[root@ivan ~]# tcpdump -nn -i lo -A port 8000tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes09:10:42.114284 IP 127.0.0.1.33296 > 127.0.0.1.8000: Flags [S], seq 3390176024, win 43690, options [mss 65495,sackOK,TS val 830366494 ecr 0,nop,wscale 8], length 0E..<..@.@..............@.............0.........1~c.........09:10:42.114293 IP 127.0.0.1.8000 > 127.0.0.1.33296: Flags [S.], seq 4147196208, ack 3390176025, win 43690, options [mss 65495,sackOK,TS val 830366494 ecr 830366494,nop,wscale 8], length 0E..<..@.@.<..........@...110.........0.........1~c.1~c.....09:10:42.114302 IP 127.0.0.1.33296 > 127.0.0.1.8000: Flags [.], ack 1, win 171, options [nop,nop,TS val 830366494 ecr 830366494], length 0E..4..@.@..............@.....111.....(.....1~c.1~c.09:10:42.114329 IP 127.0.0.1.33296 > 127.0.0.1.8000: Flags [P.], seq 1:88, ack 1, win 171, options [nop,nop,TS val 830366494 ecr 830366494], length 87E.....@.@..b...........@.....111...........1~c.1~c.GET / HTTP/1.0Host: testConnection: closeUser-Agent: curl/7.64.1Accept: */*09:10:42.114333 IP 127.0.0.1.8000 > 127.0.0.1.33296: Flags [.], ack 88, win 171, options [nop,nop,TS val 830366494 ecr 830366494], length 0E..4R/@.@............@...111...p.....(.....1~c.1~c.09:10:42.115062 IP 127.0.0.1.8000 > 127.0.0.1.33296: Flags [P.], seq 1:155, ack 88, win 171, options [nop,nop,TS val 830366494 ecr 830366494], length 154E...R0@.@............@...111...p...........1~c.1~c.HTTP/1.0 200 OKServer: SimpleHTTP/0.6 Python/3.7.2Date: Mon, 07 Sep 2020 13:10:42 GMTContent-type: text/html; charset=utf-8Content-Length: 34009:10:42.115072 IP 127.0.0.1.33296 > 127.0.0.1.8000: Flags [.], ack 155, win 175, options [nop,nop,TS val 830366494 ecr 830366494], length 0E..4.@.@..............@...p.11......(.....1~c.1~c.09:10:42.115094 IP 127.0.0.1.8000 > 127.0.0.1.33296: Flags [P.], seq 155:495, ack 88, win 171, options [nop,nop,TS val 830366494 ecr 830366494], length 340E...R1@.@..<.........@...11....p.....|.....1~c.1~c.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Directory listing for /</title></head><body><h1>Directory listing for /</h1><hr><ul><li><a href="test_file">test_file</a></li></ul><hr></body></html>09:10:42.115098 IP 127.0.0.1.33296 > 127.0.0.1.8000: Flags [.], ack 495, win 180, options [nop,nop,TS val 830366494 ecr 830366494], length 0E..4.@.@..............@...p.13......(.....1~c.1~c.09:10:42.115128 IP 127.0.0.1.8000 > 127.0.0.1.33296: Flags [F.], seq 495, ack 88, win 171, options [nop,nop,TS val 830366494 ecr 830366494], length 0E..4R2@.@............@...13....p.....(.....1~c.1~c.09:10:42.115264 IP 127.0.0.1.33296 > 127.0.0.1.8000: Flags [F.], seq 88, ack 496, win 180, options [nop,nop,TS val 830366495 ecr 830366494], length 0E..4..@.@..............@...p.13 .....(.....1~c.1~c.09:10:42.115271 IP 127.0.0.1.8000 > 127.0.0.1.33296: Flags [.], ack 89, win 171, options [nop,nop,TS val 830366495 ecr 830366495], length 0E..4R3@.@............@...13 ...q.....(.....1~c.1~c.^C12 packets captured24 packets received by filter0 packets dropped by kernel


При неправильной конфигурации (с портом 8009 в апстриме nginx) на порту 8000 никакого трафика не было. Ваня обрадовался: теперь даже если разработчики забыли реализовать запись в лог при сетевых ошибках, всё равно можно хотя бы узнать, идёт ли трафик на нужный хост или порт.

Какой вывод можно сделать из этой истории? Даже если логов нет, в Linux есть утилиты, которые могут помочь с локализацией проблем.

А если не сеть?


Всё хорошо работало, но однажды Ваня снова получил ошибку, на этот раз другую:

MacBook-Pro-Ivan:~ ivantester$ curl http://12.34.56.78<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd"><html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><title>Error response</title></head><body><h1>Error response</h1><p>Error code: 404</p><p>Message: File not found.</p><p>Error code explanation: HTTPStatus.NOT_FOUND - Nothing matches the given URI.</p></body></html>

Ваня снова зашёл на сервер, но в этот раз проблема не была связана с сетью. В логе сервиса тоже было написано File not found, и Ваня решил разобраться, почему внезапно появилась такая ошибка. Он знает, что есть процесс python3 -m http.server, но не знает, содержимое какой директории выводит этот сервис (или, другими словами, какая у этого процесса current working directory). Он узнаёт это с помощью команды lsof:

[root@ivan ~]# ps aux | grep python | grep "http.server"root   20638 0.0 0.3 270144 13552 pts/2  S+  08:29  0:00 python3 -m http.server[root@ivan ~]# lsof -p 20638 | grep cwdpython3 20638 root cwd  DIR   253,1   4096 1843551 /root/test_dir_srv2

Также это можно сделать с помощью команды pwdx или с помощью директории proc:

[root@ivan ~]# pwdx 2063820638: /root/test_dir_srv2[root@ivan ~]# ls -l /proc/20638/cwdlrwxrwxrwx 1 root root 0 Aug 31 08:37 /proc/20638/cwd -> /root/test_dir_srv2

Такая директория действительно есть на сервере, и в ней лежит файл с именем test_file. В чём же дело? Иван погуглил и нашёл утилиту strace, с помощью которой можно смотреть, какие системные вызовы выполняет процесс (про strace, кстати, есть хорошая статья на Хабре, и даже не одна). Можно либо запускать новый процесс через strace, либо подключаться этой утилитой к уже запущенному процессу. Ване подходил второй вариант:

Вывод утилиты strace
[root@ivan ~]# strace -ff -p 20638strace: Process 20638 attachedrestart_syscall(<... resuming interrupted poll ...>) = 0poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 1 ([{fd=4, revents=POLLIN}])accept4(4, {sa_family=AF_INET, sin_port=htons(57530), sin_addr=inet_addr("127.0.0.1")}, [16], SOCK_CLOEXEC) = 5clone(child_stack=0x7f2beeb28fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f2beeb299d0, tls=0x7f2beeb29700, child_tidptr=0x7f2beeb299d0) = 21062futex(0x11204d0, FUTEX_WAIT_PRIVATE, 0, NULLstrace: Process 21062 attached<unfinished ...>[pid 21062] set_robust_list(0x7f2beeb299e0, 24) = 0[pid 21062] futex(0x11204d0, FUTEX_WAKE_PRIVATE, 1) = 1[pid 20638] <... futex resumed> )    = 0[pid 20638] futex(0x921c9c, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 27, {1598879772, 978949000}, ffffffff <unfinished ...>[pid 21062] futex(0x921c9c, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x921c98, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1[pid 20638] <... futex resumed> )    = 0[pid 20638] futex(0x921cc8, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>[pid 21062] futex(0x921cc8, FUTEX_WAKE_PRIVATE, 1) = 1[pid 20638] <... futex resumed> )    = 0[pid 20638] futex(0x921cc8, FUTEX_WAKE_PRIVATE, 1) = 0[pid 20638] poll([{fd=4, events=POLLIN}], 1, 500 <unfinished ...>[pid 21062] recvfrom(5, "GET / HTTP/1.1\r\nConnection: upgr"..., 8192, 0, NULL, NULL) = 153[pid 21062] stat("/root/test_dir_srv/", 0x7f2beeb27350) = -1 ENOENT (No such file or directory)[pid 21062] open("/root/test_dir_srv/", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)[pid 21062] write(2, "127.0.0.1 - - [31/Aug/2020 09:16"..., 70) = 70[pid 21062] write(2, "127.0.0.1 - - [31/Aug/2020 09:16"..., 60) = 60[pid 21062] sendto(5, "HTTP/1.0 404 File not found\r\nSer"..., 184, 0, NULL, 0) = 184[pid 21062] sendto(5, "<!DOCTYPE HTML PUBLIC \"-//W3C//D"..., 469, 0, NULL, 0) = 469[pid 21062] shutdown(5, SHUT_WR)    = 0[pid 21062] close(5)          = 0[pid 21062] madvise(0x7f2bee329000, 8368128, MADV_DONTNEED) = 0[pid 21062] exit(0)           = ?[pid 21062] +++ exited with 0 +++<... poll resumed> )          = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500)  = 0 (Timeout)poll([{fd=4, events=POLLIN}], 1, 500^Cstrace: Process 20638 detached<detached ...>


Обычно вывод strace довольно объёмный (а может быть и очень большим), поэтому удобнее сразу перенаправлять его в файл и потом уже искать в нём нужные системные вызовы. В данном же случае можно сразу обнаружить, что сервис пытается открыть директорию /root/test_dir_srv/ кто-то переименовал её и не перезапустил после этого сервис, поэтому он возвращает 404.

Если сразу понятно, какие именно системные вызовы нужно посмотреть, можно использовать опцию -e:

[root@ivan ~]# strace -ff -e trace=open,stat -p 20638strace: Process 20638 attachedstrace: Process 21396 attached[pid 21396] stat("/root/test_dir_srv/", 0x7f2beeb27350) = -1 ENOENT (No such file or directory)[pid 21396] open("/root/test_dir_srv/", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)[pid 21396] +++ exited with 0 +++^Cstrace: Process 20638 detached

Вывод: иногда можно немножко залезть под капот процессу, а помогает с этим strace. Так как эта утилита выводит все системные вызовы, которые использует процесс, то с её помощью также можно находить и сетевые проблемы (например, к какому хосту/порту пытается подключиться процесс), что делает её довольно универсальным инструментом дебага. Также существует похожая утилита ltrace.

Есть ли что-то ещё?


Ваня на этом не остановился и узнал, что есть GNU Project Debugger GDB. С его помощью можно залезть в процесс и даже немного модифицировать его. И Ваня решил попробовать обнаружить последнюю ошибку с помощью GDB. Он предположил, что раз сервис выводит содержимое директории, то можно попробовать поставить breakpoint на функции open() и посмотреть, что будет:
Вывод утилиты gdb
[root@ivan ~]# gdb -p 23998GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-119.el7Copyright (C) 2013 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-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.Attaching to process 23998 <здесь много сообщений о загрузке символов и отсутствии debugging symbols...>...0x00007f2284c0b20d in poll () at ../sysdeps/unix/syscall-template.S:8181T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)Missing separate debuginfos, use: debuginfo-install keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.15.1-34.el7.x86_64 libcom_err-1.42.9-13.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libselinux-2.5-14.1.el7.x86_64 openssl-libs-1.0.2k-16.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64(gdb) set follow-fork-mode child(gdb) b openBreakpoint 1 at 0x7f2284c06d20: open. (2 locations)(gdb) cContinuing.[New Thread 0x7f227a165700 (LWP 24030)][Switching to Thread 0x7f227a165700 (LWP 24030)]Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:8181T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)(gdb) n83T_PSEUDO_END (SYSCALL_SYMBOL)(gdb) n_io_FileIO___init___impl (opener=<optimized out>, closefd=<optimized out>, mode=<optimized out>, nameobj=0x7f227a68f6f0, self=0x7f227a68f6c0) at ./Modules/_io/fileio.c:381381                Py_END_ALLOW_THREADS(gdb) n379                self->fd = open(name, flags, 0666);(gdb) n381                Py_END_ALLOW_THREADS(gdb) print name$1 = 0x7f227a687c90 "/root/test_dir_srv/"(gdb) qA debugging session is active.Inferior 1 [process 23998] will be detached.Quit anyway? (y or n) yDetaching from program: /usr/local/bin/python3.7, process 23998[Inferior 1 (process 23998) detached]


После команды c (continue) Ваня в другой консоли запустил curl, попал в дебаггере в точку останова и стал выполнять эту программу (то есть сервис) по шагам. Как только он нашёл в коде open по какому-то пути name, он вывел значение этой переменной и увидел /root/test_dir_srv/.
GDB это мощный инструмент, и здесь описан простейший вариант его использования. Иногда он может помочь в воспроизведении каких-либо сложных кейсов (например, можно приостановить процесс в нужный момент и воспроизвести состояние гонки), также он помогает с чтением core dump файлов.

А что если Docker?


В один момент DevOps решили, что сервис теперь будет деплоиться Docker-контейнером, и нужно было провести ретест всех кейсов, которые нашёл Ваня. Ваня без проблем нагуглил следующее:

  1. Использовать tcpdump, strace и gdb можно и внутри контейнера, но нужно иметь ввиду Linux capabilities (есть статья, которая объясняет, почему strace не работал в контейнере без --cap-add=SYS_PTRACE).
  2. Можно использовать опцию --pid.

Но ему стало интересно, можно ли посмотреть весь трафик, идущий в контейнер (или из контейнера), прям с хоста. У tcpdump есть возможность выводить трафик какого-либо интерфейса (опция -i), каждому контейнеру соответствует один виртуальный интерфейс veth (это можно увидеть, например, через ifconfig или ip a), но как понять, какому контейнеру какой интерфейс соответствует? Если контейнер не использует host networking, то внутри него будет сетевой интерфейс eth0, через который он может общаться по сети с другими контейнерами и хостом. Остаётся лишь найти, ifindex какого интерфейса на хосте совпадает с iflink интерфейса eth0 контейнера (что это означает можно почитать здесь).

[root@ivan ~]# for f in `ls /sys/class/net/veth*/ifindex`; do echo $f; cat $f; done | grep -B 1 `docker exec test_service_container cat /sys/class/net/eth0/iflink` | head -1/sys/class/net/veth6c18dba/ifindex

Теперь можно запускать tcpdump для интерфейса veth6c18dba:

tcpdump -i veth6c18dba

Но есть способ проще: можно найти IP-адрес контейнера в его сети и слушать трафик на нём:

[root@ivan ~]# docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' test_service_container172.17.0.10[root@ivan ~]# tcpdump -i any host 172.17.0.10

Вывод: дебаг в Docker-контейнере это не страшно. Утилиты в нём работают, а для чтения логов можно использовать docker logs.

Выводы


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

  • Логи лучший друг человека. Если встречается неожиданное поведение сервиса и при этом он не пишет ничего в лог это повод попросить разработчиков добавить логов.
  • Иногда бывает, что локализовать ошибку надо, даже если в логах ничего нет. К счастью, в Linux есть много утилит, которые помогают с этим.
  • С дебагом любых сетевых коммуникаций помогает tcpdump. Он помогает видеть, какой трафик откуда и куда идёт на сервере.
  • Заглянуть внутрь процесса помогают утилиты strace, ltrace и gdb.
  • Всё это можно использовать, если сервис работает в Docker-контейнере.
  • Много информации о процессе есть в директориях /proc/PID. Например, в /proc/PID/fd находятся симлинки на все открытые процессом файлы.
  • Также помочь получить различную информацию о системе, файлах или процессах могут утилиты ps, ls, stat, lsof, ss, du, top, free, ip, ldconfig, ldd и прочие.

Надеюсь, вам была полезна эта история, и хотя бы однажды она поможет вам понять, в чём дело, когда вы будете что-то дебажить в Linux. Если Ваня что-то упустил, делитесь этим в комментариях!
Подробнее..

Команды GDB, о которых вы возможно не знали

19.03.2021 00:20:03 | Автор: admin


Отладка кода это как охота. Охота на баги.
Amit Kalantri

Что такое GDB


GNU Debugger переносимый отладчик проекта GNU, который работает на многих UNIX-подобных системах и умеет производить отладку многих языков программирования, включая Си, C++, Free Pascal, FreeBASIC, Ada, Фортран и Rust. GDB свободное программное обеспечение, распространяемое по лицензии GPL.
Источник: GNU Debugger Википедия


Проще говоря GDB отладчик, который работает прямо из консоли.


В данной статье я опишу комманды, о которых раньше не знал, однако, они оказались очень полезными при отладке в GDB.


Рассматривать работу отладчика будем на одной из моих курсовых работ, её тема шифр Цезаря, так что не нужно удивляться названиям переменных или классов. Самого кода вы тут не увидите, ибо это будет лишним для понимания сути действия, однако стоит привести несколько замечаний для полного понимания:


Объект Что значит
Caesar::shift Свойство Caesar
Caesar::ChangeShift Метод Caesar
Caesar::Crypt Метод Caesar

Интерфейс в консоли (TUI)


Я всегда включаю TUI, как только зайду в GDB, он позволяет посмотреть где именно находится выполняемая строка, что идёт перед ней, что дальше. Включается TUI в GDB командой tui enable


С помощью колесика мыши (или стрелок) вы можете перемещаться по псевдо-интерфейсу вверху. С помощью комбинации клавиш Ctrl + p (previous), Ctrl + n (next), вы можете перемещаться между введёнными командами в терминале (окно внизу).


Тезис: Для включения псевдо-интерфейса используют команду tui enable, для выключения tui disable.


Точки останова


Точки останова можно ставить с помощью команды breakpoint (сокращение b).
Можно ставить точки останова исходя из следующих принципов:


  • Точкой останова может являться имя функции (метода) или переменной
  • Точкой останова может являться номер строки
  • Точкой останова может являться адрес памяти, в котором располагается инструкция
  • Точкой останова может являться условие

Если с тремя первыми всё понятно, то третий принцип достаточно интересный.


b Method if variable == value примерно такой синтаксис используется для задания точки останова исходя из выполнения условия.


Например:


(gdb) b Crypt if Caesar::shift == 1Note: breakpoint 2 also set at pc 0x4026e3.Breakpoint 4 at 0x4026e3: file Caesar.h, line 55.

Тут сказано поставить точку останова на методе Crypt(), если свойство класса Caesar под названием shift будет равно 1.


Дополнительно: точки останова можно удалять с помощью команды delete, или её сокращения d.


Тезис: Точки останова можно ставить четыремя способами: с помощью имени объекта, с помощью номера строки, с помощью адреса, с помощью условия.


Шаг с входом в Scope, шаг без входа


Различие между командами next и step (сокращения: n и s) состоит в том, что next выполняет код не входя в Scope (не входя внутрь функции или метода), тогда как step просто выполняет шаг и аналогичен команде Step In в обычных дебагерах.


Дополнительно: для выполнения программы до точки останова используют continue, для выхода из функции в которую вы вошли используют finish или fin.


Тезис: Для вхождения в функцию используется step, для обычного выполнения next.


Просмотр переменных разными способами


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


Команда display


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


(gdb) display Caesar::shift1: Caesar::shift = <error: Cannot reference non-static field "shift">(gdb) nBreakpoint 2, Caesar::Crypt (this=0x7fffffffdc30, text="Crypt this text! Btw, xyz") at Caesar.h:551: Caesar::shift = 1(gdb) n1: Caesar::shift = 1(gdb)

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


(gdb) display *stringArrayForOutput@lengthOfArray1: *stringArrayForOutput@lengthOfArray = {"Etarv vjku vgzv! Dvy, zab", "Fubsw wklv whaw! Ewz, abc", "Gvctx xlmw xibx! Fxa, bcd"}(gdb) 

Думаю не стоит говорить, что массив может быть динамический и по мере выполнения программы lengthOfArray (длина массива) может изменяться. GDB сам разберётся с этим и всё время будет выводить актуальную информацию.


Команда explore


Иногда бывает такое, что информации до того много, что путаешься какой объект за что отвечает, для этого создали explore.


explore помогает найти что за объект мелькает перед вашими глазами и рассказывает о нём всё, что может


(gdb) explore Caesar::shift'Caesar::shift' is a scalar value of type 'int'.Caesar::shift = 6(gdb) explore Caesar'Caesar' is a struct/class with the following fields:letters = <Enter 0 to explore this field of type 'std::string'>shift = <Enter 1 to explore this field of type 'int'>

Тезис: Кроме команды print можно использовать команды explore и display.


Запись логов работы программы


Иногда нужно "перемотать вспять" момент, где случилась ошибка, для этого придумана команда record, которая может полностью записывать логи программы в файл или просто в память, дабы перемотать некоторые моменты вспять или посмотреть что именно вызвало ошибку


(gdb) target record-full(gdb) p Caesar::shift$1 = 6(gdb) n(gdb) p Caesar::shift$2 = 7(gdb) reverse-nextNo more reverse-execution history.(gdb) p Caesar::shift$3 = 6

Для того, чтобы остановить запись и удалить все логи нужно ввести record stop, для того, чтобы сохранить все логи в файл нужно ввести record save <имя файла>.


Тезис: GDB имеет возможность создать лог работы программы, с помощью которого, мы можем перемещаться по программе вспять.


Просмотр информации


Просмотреть сразу все локальные переменные можно с помощью info local:


(gdb) info localmainObj = {letters = "abcdefghijklmnopqrstuvwxyz", shift = 1}textForC = "Crypt this text! Btw, xyz"cryptedText = "Dszqu uijt ufyu! Cux, yza"outputVariants = 0x402280 <_start>

Просмотреть все переменные, которые инициализированы можно с помощью info variables:


(gdb) info variablesAll defined variables:File /usr/include/c++/10/bits/basic_string.h:101:    const std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::size_type std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::npos;File /usr/include/c++/10/bits/stl_pair.h:83:     static const std::piecewise_construct_t std::piecewise_construct;File /usr/include/c++/10/iostream:74:     static std::ios_base::Init std::__ioinit;Non-debugging symbols:0x00000000004002e8  __abi_tag0x0000000000403000  _IO_stdin_used0x00000000004031b8  typeinfo for std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >0x00000000004031e0  typeinfo name for std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >--Type <RET> for more, q to quit, c to continue without paging--

Чтобы посмотреть все точки останова можно ввести info b:


(gdb) info b   Num     Type           Disp Enb Address            What1       breakpoint     keep y   0x0000000000402633 in Caesar::ChangeShift() at Caesar.h:31        breakpoint already hit 1 time2       breakpoint     keep y   0x00000000004026e3 in Caesar::Crypt(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) at Caesar.h:55        breakpoint already hit 1 time

Вы также можете посмотреть тип переменной с помощью команды whatis:


(gdb) whatis Caesar::shifttype = int(gdb) whatis Caesartype = Caesar

Чтобы посмотреть адрес функции, вы можете использовать info address:


(gdb) info address Caesar::CryptSymbol "Caesar::Crypt(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)" is a function at address 0x4026be.

Заключение


Есть ещё куча комманд, которые я не привёл в данной статье, однако, на мой взгляд они являются столь полезными, как те, что я перечислил выше. Вы можете почитать больше о разработке на Linux в моём телеграм-канале, если вам это нужно. Я периодически пишу о фулл-стак разработке, а также о Unix-подобных системах.


В данной статье не указаны некоторые комманды, которые легки для понимания, например: run, stop, quit, и т.д. Не указал я их специально, так как статья пытается нести информацию, о которой пользователи GDB возможно могли не знать. Если у вас есть предложения чем можно дополнить статью, с радостью почитаю комментарии.

Подробнее..

Полноценная GDB отладка через USB на плате BluePill (STM32F103С8T6)

09.03.2021 18:08:33 | Автор: admin

В данной статье речь пойдет о программировании и полноценной отладке микроконтроллера STM32F103C8T6 через USB.

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

Для начала нужно было опробовать все на прототипе. В качестве отладочной платы была выбрана почти всем знакомая BluePill на микроконтроллере STM32F103. Поскольку на данной отладке имеется интерфейс MicroUSB, было принято решение в прототипе использовать именно этот интерфейс. В будущем предполагался переход на UART подключенный к GSM модулю.

Требовалось реализовать загрузчик имеющий несколько функциональных блоков. Задачу на подзадачи и решать их последовательно. Рассмотрим их.

  1. Драйвер интерфейса USB со стороны микроконтроллера.

  2. Код обновления прошивки микроконтроллера с помощью GDB.

  3. GDB сервер.

  4. Вывод отладочных логов.

Обо все по порядку. Для прототипирования был реализован загрузчик (bootloader).

1. Первым был реализован протокол отладочного интерфейса. Т.е. USB. В качестве класса USB-устройств был выбран WinUSB. Для его реализации я использовал воспользовался исходным кодом библиотеки libopencm3. Для этого необходимо описать дескриптор устройства, дескрипторы конфигурации, интерфейса, конечных точек, а так же дескрипторы содержащие стоки "MSFT100" и "WINUSB". Последние два дескриптора требуются для определения устройства как WinUSB. Конфигурация конечных точек (USB-Endpoint) выбрана следующим образом control endpoint 0, bulk out endpoint 1, bulk in endpoint 81, bulk in endpoint 82. Конечная точка с номером ноль присутствует во всех устройствах USB, endpoint 1- применяется для передачи команд в загрузчик микроконтроллера, endpoint 81 - для передачи ответов на команды на компьютер, а 82 - для передачи текстовой информации (логов). Подробнее о USB можно прочитать в публикациях из разряда USB in a NutShell.

2. Требовался код работы с флеш памятью. Вы можете подумать что тут все просто. Это так и не так одновременно. Первая проблема, которая возникла,- невозможность стереть флеш память в обработчике прерывания. Дело в том, что архитектура Cortex M предусматривает два режима работы процессора. Thread и Handler. В первом режиме процессор находится после старта, а так же когда нет активных прерываний. В Handler mode исполняются все обработчики исключений и прерываний. К сожалению, стирание flash-памяти на STM32F103C8T6 в Handler режиме приводит к корректному статусу стирания памяти, но сама память не стирается.

Эта проблема решается посредством запуска кода стирания Flash в Thread режиме. Сделать это можно, так, как обычно происходит в операционных системах. Для этого нужно понимать что такое контекст потока. Это состояние набора регистров процессора, стека, описывающий конкретный момент работы системы. при входе в обработчик прерывания сохраняется контекст текущего работающего потока, а при выходе из обработчика, он восстанавливается и выполнение программы продолжается. Нам при выходе из обработчика нужно лишь восстановить "свой" контекст начала функции стирания Flash памяти. Подобная операция происходит в операционных системах при переключении задач.

Другая проблема является более сложной. Заключается она в том, что при работе с флеш памятью может происходить выполнение обработчика прерывания, той прошивки, которая находится под отладкой. Эта проблема решается несколькими действиями перед стиранием памяти. Первое что требуется сделать,- заблокировать вызов любых обработчиков прерываний, используемых в отлаживаемой прошивке. Или проще говоря тех, которые не используются в Bootloader-e. Но даже в этом случае команда на стирание памяти может поступить в то время, когда один из обработчиков уже выполняется. Для решения этого вопроса и решил воспользоваться пошаговым режимом работы процессора и в таком режиме вывести процессор из всех обработчиков прерываний. После этого флеш-память можно стирать.

3. Требовалось реализовать GDB-сервер. Я воспользовался исходным кодом проекта BlackMagic, для обработки команд приходящих из среды разработки. На самом деле приходящих от приложения arm-none-eabi-gdb. Далее команды транслировались в команды бинарного протокола, который используется для п процессе взаимодействия с микроконтроллером. Нижний уровень GDB-сервера выполнен с использованием библиотеки WinUSB.

4. После того как прототип заработал, я пришел к решению добавить вывод отладочной информации с использованием printf. Для передачи отладочных сообщений использовал endpoint 82. На самом деле 8 - это единица в старшем разряде, указывающая направление передачи данных по шине USB в сторону компьютера (Host-а).

Но таким образом функцией printf можно было пользоваться только в bootloader-е. А как же быть с отлаживаемым приложением? Обычно для взаимодействия с операционной системой используются прерывания/системные вызовы. Так BIOS использова int13, ms-dos int21. Мы же на микроконтроллере воспользуемся системным вызовом, т.е. командой svc. При выполнении данной команды в прошивке, будет вызван обработчик прерывания SVC, находящийся в bootloader-е. Что нам и требовалось сделать.

Bootloader использует 10Kb flash памяти, но зарезервировано 16Kb с целью расширения функционала. Так же используется 4K оперативной памяти. Оперативная память применяется для хранения буферов USB, контекста прерванного процесса, а так же как память стека обработчиков прерываний. Итого. Остается 16Kb из 20Kb оперативной памяти и 48Kb flash памяти. Хотя на самом деле flash-память в контроллере STM32F103C8T6 не 64Kb а 128Kb,- соответственно остается 112Kb.

В процессе отладки прошивки, возникает один интересный момент. Если, в отладчике делать шаг на потоке, а в это время произойдет вызов обработчика прерывания, то отладчик шагнет в обработчик прерывания. Чтобы такого не происходило, в коде я использовал step режим для выхода из обработчика прерываний. При этом если отладчик в прерывании наткнется на точку останова, будет произведена остановка отладки на точке останова.

И наконец, - что поддерживается:

  1. Загрузка прошивки на плату с использованием GDB. Т.е. непосредственно из среды программирования/отладки. В моем случае это STM32CubeIDE. Адрес вектора прерываний должен находится по адресу 0x8004000.

  2. Просмотр и изменение памяти.

  3. Просмотр и изменение регистров периферии.

  4. Восемь точек останова.

  5. Режим пошаговой отладки.

  6. Принудительная остановка.

В отлаживаемой прошивке нельзя изменять адрес вектора обработчика прерываний. Нельзя изменять приоритеты прерываний. Приоритет должен быть выше или равен 0x40 по значению. Нельзя запрещать прерывания systick, прерывание usb, и прерывания DebugMon, SvcHandler, а так же всех FaultHandler-s.

Код прототипа проекта доступен по ссылке

Видео работы с платой в среде STM32CubeIDE

Подробнее..

Шпаргалка полезных команд GDB

03.01.2021 08:10:12 | Автор: admin

Для кого она нужна?


1) начинающих реверсеров, знающих особенности обратного проектирования, и желающих изучить такой отладчик как GDB


2) как подсказка тем кто постоянно работает с IDA, Ghidra или любым другим мощным и надежным инструментом, но в силу тех или иных обстоятельств решить задачу проще и быстрее с помощью GDB, и не очень хочется залезать в официальную документацию и снова все вспоминать


Основные команды


Запуск


Общий синтаксис выбора исполняемого файла для анализа


gdb program_name

Запустить выполнение программы


run | r

Присоединиться к gdbserver


target remote host:port

Присоединиться к процессу, отключиться от него


attach PID / detach

Выйти из gdb


quit | qCTRL + D

Статический анализ


Выбрать синтаксис ассемблера


set disassembly-flavor intel/att

Просмотреть информацию об архитектуре, секциях


info file 

Получение списка функций


info functions | i func

Получение asm-листинга функции


disas func_namedisas address 

Если у вас есть исходники (можем собрать с опцией -g3 для gcc) или исследуемая программа содержит отладочную информацию, можем посмотреть листинг ее исходного кода


list func_name

Динамический анализ


Установить аргументы для каждого запуска программы и посмотреть их


set argsshow args

Распределение виртуальной памяти


info proc mappings

Просмотр регистров


registers

Отладка

Шаг с заходом в функцию


step | s

Шаг с прыжком через вызываемую подпрограмму


next | n

Выполнить до нужной строки, адреса


until | u number_of_list_stringuntil | u *func_name+offsetuntil | u *address

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


info argsinfo localsinfo frame

Просмотреть список процессов и выбрать интересующий


info threadsthread number

Способы расстановки breakpoints


b func_nameb *func_name+offsetb *address

Посмотреть список точек останова, включить или отключить, удалить breakpoint


info breakdisable/enable breakpoint_numberdelete breakpoint_numberignore breakpoint_number n  //  остановится на этой точке пройдя ее n раз

Продолжить выполнение до следующего breakpoint-а


continue | c

Просмотр стека


telescopetelescope $rsp+64

Для отображения значения по указанному адресу используется команда x, где через "/" указывается формат вывода


x/i - инструкцияx/x - hexx/s - строкаx/a - адрес

а также размер вывода


x/b - 8-bitx/h - 16-bitx/w - 32-bitx/g - 64-bit

Пример


x/64bxx/i $pc

Передача аргумента командной строки


run $(python -c "print('A'*32 + '\xde\xad')")run $(echo "asdf\\xde\xad")

Для передачи значений функциям ввода


run <<< $(python -c "print('A1'*3)")run <<< $(echo "asdf\xde\xad")

Gdb Сервер

Запустить сервер gdb для отладки


gdbserver host:port program

Reverse Debug

Все мы проходили через этот неловкий момент когда во время отладки мы проскочили интересующую нас функцию, и теперь снова надо перезапускать отладчик, проходить тот же путь на CFG и т.п. Чтобы избежать этого, в gdb есть такая фишка как Reverse Debug, позволяющая сохранить состояние программы и обратно отладить до него.


Для этого, после запуска отладчика укажем gdb, что хотим начать использовать reverse debug и стоит сохранять состояния программы


record

После этого станут доступны следующие команды


reverse-stepreverse-next

Создание дампа

Сдампить участок памяти ( часто необходимо при работе с распаковщиками )


dump memory output_file start_addr end_addr

Настройка для работы


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


display/5i $pcdisplay/g $raxdisplay/g $rbxdisplay/g $rcx

Делаем жизнь проще с GEF


Для эффективного использования gdb лучше воспользоваться плагином gef, он уже включает в себя удобный закрепленный вывод, используемый при динамическом анализе, а также набор собственных команд расширяющий возможности нашего универсального отладчика. Рассмотрим некоторые наиболее полезные.


Посмотреть состояние aslr, включить/отключить


aslraslr on/off

Для проверки исполняемого файла на наличие ASLR, Canary, PIE и т.д.


checksec

Посмотреть чанки


heap chunks

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


canary

Чуть более удобный вывод, чем info proc mappings


vmmap

Просмотр регистра флагов и изменение их


flagsflags -Flag_name +Flag_name

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


format-string-helper

Создание паттерна и его поиск


pattern create 128pattern search 0x61616167pattern search $rbp

Поиск строк по шаблону


search-pattern pattern

Патчинг


patch byte/word/dword/qword address value

Печать массива в формате удобном для копирования в python код. Параметр b должен быть 8/16/32/64, l контролирует длину массива


Пример


print-format -b 64 -l 1  $rsp

Для поиска шеллкода по шаблону


shellcode search patternshellcode get shellcode_number

Ксорим значения в памяти и регистрах


xor display address/register size xor_keyxor patch address/register size xor_key
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru