Условия, шаг внутрь, перезапуск
На странице 3 точка срабатывала на каждом проходе. На десяти строках ввода это терпимо, на десяти тысячах — нет. Здесь — как остановиться ровно там, где нужно, заглянуть внутрь вызова и начать заново, не выходя из отладчика.
Условие на точке
Ситуация. На странице 2 печать показала, что дефект — в седьмой строке ввода. Хочется остановиться на седьмом проходе сразу, без шести лишних continue.
Команда condition привязывает к точке условие. Точка срабатывает, только когда условие истинно. Условие пишется так же, как в if.
priemka — снова оригинал: cp ~/.course/orig/day09/priemka/main.go .
Что увидите.
stagiaire@lab:~/gocourse/day09/priemka$ dlv debug -r input.txt
Type 'help' for list of commands.
(dlv) break main.go:20
Breakpoint 1 set at 0x4b98e2 for main.main() ./main.go:20
(dlv) condition 1 i == 7
(dlv) continue
> [Breakpoint 1] main.main() ./main.go:20 (hits goroutine(1):1 total:1) (PC: 0x4b98e2)
15: total := 0
16: i := 0
17: for scanner.Scan() {
18: i++
19: line := scanner.Text()
=> 20: qty, err := strconv.Atoi(line)
21: if err != nil {
22: continue
23: }
24: boxes++
25: total += qty
(dlv) print line
"14 "Как это читать. condition 1 i == 7 ничего не ответила — значит, условие принято. Первая же остановка — седьмой проход: total:1, остановок до этого не было. print line показывает строку в кавычках, как %q: пробел после 14 виден без единой правки кода.
Шаг внутрь и наружу
Строка 20 вызывает функцию strconv.Atoi. next выполнил бы её целиком. step заходит внутрь вызываемой функции:
(dlv) step
> strconv.Atoi() /usr/local/go/src/strconv/number.go:146 (PC: 0x495813)
141: }
142: return x, nil
143: }
144:
145: // Atoi is equivalent to ParseInt(s, 10, 0), converted to type int.
=> 146: func Atoi(s string) (int, error) {
147: x, err := strconv.Atoi(s)
148: if err != nil {
149: return x, toError("Atoi", s, 0, 0, err)
150: }
151: return x, nil
(dlv) stepout
> main.main() ./main.go:20 (PC: 0x4b98fa)
Values returned:
~r0: 0
~r1: error(*strconv.NumError) *{
Func: "Atoi",
Num: "14 ",
Err: error(*errors.errorString) *{
s: "invalid syntax",},}
15: total := 0
16: i := 0
17: for scanner.Scan() {
18: i++
19: line := scanner.Text()
=> 20: qty, err := strconv.Atoi(line)
21: if err != nil {
22: continue
23: }
24: boxes++
25: total += qty
(dlv) next
> main.main() ./main.go:21 (PC: 0x4b993a)
16: i := 0
17: for scanner.Scan() {
18: i++
19: line := scanner.Text()
20: qty, err := strconv.Atoi(line)
=> 21: if err != nil {
22: continue
23: }
24: boxes++
25: total += qty
26: }
(dlv) print err
error(*strconv.NumError) *{
Func: "Atoi",
Num: "14 ",
Err: error(*errors.errorString) *{
s: "invalid syntax",},}Как это читать.
- После
stepна экране не ваш файл, а/usr/local/go/src/strconv/number.go— исходный код пакетаstrconv. Стандартная библиотека написана на том же Go. Свои функции вы начнёте писать в блоке 3, иstepбудет заходить в них. stepout— досчитать текущую функцию и вернуться туда, откуда её вызвали.Values returned— что она вернула: число0и ошибку. В ошибкеNum: "14 "— какой текст не удалось разобрать,invalid syntax— почему.- Стрелка снова на строке 20: вызов закончился, но результат ещё не записан в
qtyиerr.nextзавершает строку, иprint errпоказывает ту же ошибку.
Правило: next — «не заходить», step — «зайти». Зашли случайно — stepout.
Список точек, удаление, перезапуск
Сеанс продолжается:
(dlv) breakpoints
Breakpoint runtime-fatal-throw (enabled) at 0x…,0x…,0x… for (multiple functions)() <multiple locations>:0 (0)
Breakpoint unrecovered-panic (enabled) at 0x44dba4 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1470 (0)
print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0x4b98e2 for main.main() ./main.go:20 (1)
cond i == 7
(dlv) clear 1
Breakpoint 1 cleared at 0x4b98e2 for main.main() ./main.go:20
(dlv) continue
Коробок: 8
Штук: 111
Process 1155007 has exited with status 0
(dlv) restart
Process restarted with PID 1155261
(dlv) break main.go:27
Breakpoint 2 set at 0x4b995f for main.main() ./main.go:27
(dlv) continue
> [Breakpoint 2] main.main() ./main.go:27 (hits goroutine(1):1 total:1) (PC: 0x4b995f)
22: continue
23: }
24: boxes++
25: total += qty
26: }
=> 27: fmt.Println("Коробок:", boxes)
28: fmt.Println("Штук:", total)
29: }
(dlv) print boxes
8
(dlv) exitКак это читать.
- Первые две точки в списке поставил сам отладчик: он остановится на них, если программа аварийно упадёт. Адреса у них машинные — на месте
0x…у вас будут свои числа, и порядок в первой строке тоже может быть другим. Ваша точка —Breakpoint 1, под ней её условие. clear 1убрал точку, иcontinueдовёл программу до конца: её вывод иhas exited with status 0— код возврата 0.restartзапускает программу заново с тем же вводом. Точки, которые остались, сохраняются. Новая точка получила номер 2 — номера не переиспользуются.
restart берёт уже собранную программу. Если поправить main.go во время сеанса, restart покажет старое поведение. Пересобрать и перезапустить — rebuild.
Ниже — другой сеанс, начатый заново: точек останова в нём нет, поэтому первый continue доводит программу до конца. Пока отладчик стоял на приглашении, в другом окне в main.go поменяли "Штук:" на "Штук всего:" — rebuild подхватил правку:
stagiaire@lab:~/gocourse/day09/priemka$ dlv debug -r input.txt
Type 'help' for list of commands.
(dlv) continue
Коробок: 8
Штук: 111
Process 2902945 has exited with status 0
(dlv) rebuild
(dlv) continue
Коробок: 8
Штук всего: 111
Process 2905791 has exited with status 0
(dlv) exitСам rebuild не напечатал ничего: живого процесса в сеансе не осталось, точек останова нет — сообщать нечего. Если процесс ещё жив, то есть программа стоит на точке, rebuild соберёт её заново, запустит новый процесс вместо прежнего и скажет об этом строкой Switch target process … — она в таблице в конце страницы.
Трассировка по остановкам
В дне 08 таблицу трассировки вы заполняли головой: шаг, строка, значения переменных после неё. Отладчик делает ту же таблицу, только значения в ней не додуманы, а прочитаны. Приём один и тот же: точка останова внутри цикла, условие с номером прохода — и на каждой остановке print тех переменных, которые стоят в колонках.
Таблица говорит больше, чем одно значение. Три остановки подряд с одним и тем же остатком — значит заказы не проходят, и причину надо искать в условии, а не в вычитании. Одна строка такого не покажет.
Образец — шапка и две строки (номера заказов у вас свои):
# n | qty | stock | refused
14 | 9 | 128 | 0
15 | 141 | 119 | 0stock в строке — остаток до этого заказа: отладчик останавливается перед строкой со стрелкой =>, и она ещё не выполнена. refused — сколько отказов было до него. В примере заказ 14 на 9 штук прошёл, поэтому к заказу 15 остаток уменьшился на 9; сам заказ 15 больше остатка — дальше будет отказ.
Попробуйте сейчас: задание find — два значения и таблица.
Цель: снять отладчиком две величины, которых нет в выводе программы
find, и заполнить по остановкам таблицуfind/table.txt.1. Условие, заготовка таблицы и сеанс. Все номера — из
TASK.txt, у каждого свои:▶ Выполнитеcd ~/gocourse/day09/find cat TASK.txt cat table.txt dlv debug -r orders.txt2. Для q4 поставьте точку на строку 33 и условие на
nс номером изTASK.txt, дойдите до неё и посмотритеstock. После(dlv):▶ Выполните в отладчике · по одной команде в ответ на (dlv), не в оболочку · выделенное замените своимbreak main.go:33 condition 1 n == НОМЕР continue print stockВместо
НОМЕР— номер заказа из вопроса q4 вTASK.txt.3. Таблица — те же пять заказов, что уже вписаны в
table.txt. Для каждого: поменять условие на точке, начать программу заново и снять три значения.restartобязателен — заказ с меньшим номером в текущем прогоне уже пройден:▶ Выполните в отладчике · по одной команде в ответ на (dlv), не в оболочку · выделенное замените своимcondition 1 n == НОМЕР_СТРОКИ_ТАБЛИЦЫ restart continue print qty print stock print refusedПять строк таблицы — пять таких заходов. Числа впишите вместо
?вfind/table.txt; номераnв файле уже стоят, их не меняйте.4. Для q5 уберите точку, поставьте новую на строку 34 и дойдите до первой остановки.
restartначинает программу заново — так остановка точно будет первой.main.goвfindне меняйте. Команды — в том же сеансе, после(dlv), по одной:▶ Выполните в отладчике · по одной команде в ответ на (dlv), не в оболочкуclear 1 break main.go:34 restart continue print n print stock exit5. Сдайте оба ответа:
▶ Выполните · выделенное замените своимcourse answer day09.q4 ЧИСЛО course answer day09.q5 ЧИСЛО ЧИСЛОВ строке q4 —
stockиз шага 2. В строке q5 — два числа через пробел: сначалаn, потомstockиз шага 4.Готово, когда: пункты
q4,q5иtableвcourse check 09зелёные.
Попробуйте сейчас: bug2 — locals и вопрос q3.
Цель: увидеть в
locals, какая переменнаяbug2хранит не то, ответить на вопрос q3 и починить программу.1. Условие, вопрос q3 и прогон:
▶ Выполнитеcd ~/gocourse/day09/bug2 cat TASK.txt go run . < input.txt dlv debug -r input.txt2. Остановитесь внутри
ifна строке 32 и сравните вlocalsкаждую переменную с тем, что ожидаете поinput.txt:▶ Выполните в отладчике · по одной команде в ответ на (dlv), не в оболочкуbreak main.go:32 continue locals3. Вопрос q3: точка на строке 30 с условием на
count— номер берите изTASK.txt. Новая точка получит номер 2: номера не переиспользуются. На остановке —print bestиprint n:▶ Выполните в отладчике · по одной команде в ответ на (dlv), не в оболочку · выделенное замените своимclear 1 break main.go:30 condition 2 count == НОМЕР restart continue print best print n exitВместо
НОМЕР— номер предложения из вопроса q3 вTASK.txt.nиcountна этой остановке разные: во вводе есть пустые строки, иnсчитает их тоже.4. Сдайте два числа через пробел, сначала
best, потомn:▶ Выполните · выделенное замените своимcourse answer day09.q3 ЧИСЛО ЧИСЛО5. Почините
bug2минимально и посмотрите правку. Ответ q3 от починки не зависит — пересдавать его не нужно:▶ Выполнитеgo run . < input.txt diff ~/.course/orig/day09/bug2/main.go main.goГотово, когда: пункт
q3зелёный, а выводbug2наinput.txtсовпадает с «ожидалось» изTASK.txt— пунктbug2.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
Command failed: 1:3: expected '==', found '=' |
в условии одно =, а сравнение — два |
condition 1 i == 7 |
Поправили код, набрали restart, а программа ведёт себя по-старому |
restart запускает уже собранную программу |
rebuild; или exit и снова dlv debug -r input.txt |
После rebuild строка Switch target process from 2907915 to 2908761 (…/__debug_bin…) |
в сеансе ещё жил процесс прежней сборки: rebuild собрал программу заново и продолжает в новом процессе, номер PID новый. Точки останова сохранились, поэтому он сразу встаёт на ту же точку |
это нормально, ничего делать не нужно |
После step на экране код из /usr/local/go/src/... |
зашли внутрь функции стандартной библиотеки | stepout — вернуться; дальше next |
could not launch process: not an executable file |
в каталоге не package main: собралась библиотека, запускать нечего |
первая строка кода программы — package main |
Could not attach to pid 1156607: this could be caused by a kernel security setting, try writing "0" to …/yama/ptrace_scope |
вы подключаетесь dlv attach к уже запущенной программе; Ubuntu запрещает это обычному пользователю |
в /proc ничего не пишите — это защита системы. Запускайте программу из отладчика: dlv debug |
Warning: debugging optimized function над кодом на остановке |
отладчик открыл файл из обычного go build, например dlv exec ./priemka: компилятор ускорил код, и значения переменных могут показываться неверно или не показываться |
dlv debug — он собирает программу без таких ускорений |
Дальше: отладчик в VS Code — course next