День 09 · Отладчик и поиск ошибок · страница 4 из 6

Условия, шаг внутрь, перезапуск

На странице 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",},}

Как это читать.

Правило: 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

Как это читать.

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 | 0

stock в строке — остаток до этого заказа: отладчик останавливается перед строкой со стрелкой =>, и она ещё не выполнена. 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.txt

2. Для 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
exit

5. Сдайте оба ответа:

▶ Выполните · выделенное замените своим
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.txt

2. Остановитесь внутри if на строке 32 и сравните в locals каждую переменную с тем, что ожидаете по input.txt:

▶ Выполните в отладчике · по одной команде в ответ на (dlv), не в оболочку
break main.go:32
continue
locals

3. Вопрос 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