Ответ Энтони потрясающий. После его ответа я опробовал другое решение на Windows (x86-64 бит Windows). Я знаю, что этот вопрос здесь для GDB в Linux, однако я думаю, что это решение является дополнением к такого рода вопросам. Это может быть полезно для других.
Решение для Windows
В Linux вызов printf
приведет к вызову API write
. А поскольку Linux является ОС с открытым исходным кодом, мы можем отлаживать в API. Тем не менее, API отличается в Windows, он предоставляет собственный API WriteFile . Поскольку Windows является коммерческой ОС с открытым исходным кодом, точки прерывания не могут быть добавлены в API.
Но часть исходного кода VC публикуется вместе с Visual Studio, поэтому мы могли бы узнать в исходном коде, где наконец-то вызывается API WriteFile
и установить там точку останова. После отладки примера кода я обнаружил, что метод printf
может привести к вызову _write_nolock
, в котором вызывается WriteFile
. Функция находится в:
your_VS_folder\VC\crt\src\write.c
Прототип:
/* now define version that doesn't lock/unlock, validate fh */
int __cdecl _write_nolock (
int fh,
const void *buf,
unsigned cnt
)
По сравнению с write
API в Linux:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
Они имеют абсолютно одинаковые параметры. Таким образом, мы могли бы просто установить condition breakpoint
в _write_nolock
, просто сослаться на решения, приведенные выше, только с некоторыми отличиями в деталях.
Портативное решение для Win32 и x64
Очень повезло, что мы могли использовать имя параметров непосредственно в Visual Studio при установке условия для точек останова как в Win32, так и в x64. Так что очень легко написать условие:
Добавить точки останова в _write_nolock
УВЕДОМЛЕНИЕ : Есть небольшая разница в Win32 и x64. Мы могли бы просто использовать имя функции, чтобы установить расположение точек останова на Win32. Тем не менее, он не будет работать на x64, потому что при входе в функцию параметры не инициализируются. Поэтому мы не могли использовать имя параметра для установки условия точек останова.
Но, к счастью, у нас есть обходной путь: используйте местоположение в функции, а не имя функции, чтобы установить точки останова, например, первую строку функции. Параметры уже инициализированы там. (Я имею в виду использовать filename+line number
для установки точек останова или открыть файл напрямую и установить точку останова в функции, а не вход, а первая строка.)
Ограничить условие:
fh == 1 && strstr((char *)buf, "Hello World") != 0
УВЕДОМЛЕНИЕ : здесь все еще есть проблема, я протестировал два разных способа записи чего-либо в стандартный вывод: printf
и std::cout
. printf
записывает все строки в функцию _write_nolock
одновременно. Однако std::cout
будет передавать только символ за символом в _write_nolock
, что означает, что API будет вызываться strlen("your string")
раз. В этом случае условие не может быть активировано навсегда.
Решение Win32
Конечно, мы могли бы использовать те же методы, что и при условии Anthony
: установить условие точек останова с помощью регистров.
Для программы Win32 решение почти такое же, как у GDB
в Linux. Вы можете заметить, что в прототипе _write_nolock
есть украшение __cdecl
. Это соглашение о вызовах означает:
- Порядок передачи аргументов - справа налево.
- Вызывающая функция извлекает аргументы из стека.
- Соглашение об оформлении имени: символ подчеркивания (_) добавляется к имени.
- Перевод не выполнен.
Здесь есть описание здесь . И есть пример , который используется для отображения регистров и стеков на веб-сайте Microsoft. Результат можно найти здесь .
Тогда очень легко установить условие точек останова:
- Установить точку останова в
_write_nolock
.
Ограничить условие:
*(int *)($esp + 4) == 1 && strstr(*(char **)($esp + 8), "Hello") != 0
Это тот же метод, что и в Linux. Первое условие - убедиться, что строка записана в stdout
. Второй должен соответствовать указанной строке.
x64 Solution
Две важные модификации с x86 на x64 - это возможность 64-битной адресации и плоский набор из 16 64-битных регистров общего назначения.В качестве увеличения регистров x64 использует только __fastcall
в качестве соглашения о вызовах.Первые четыре целочисленных аргумента передаются в регистрах.Аргументы пять и выше передаются в стек.
Вы можете обратиться к странице Передача параметров на веб-сайте Microsoft.Четырьмя регистрами (в порядке слева направо) являются RCX
, RDX
, R8
и R9
.Поэтому очень легко ограничить условие:
Установить точку останова в _write_nolock
.
УВЕДОМЛЕНИЕ : это отличается от переносимого решениявыше, мы могли бы просто установить местоположение точки останова для функции, а не 1-й строки функции.Причина в том, что все регистры уже инициализированы на входе.
Ограничить условие:
$rcx == 1 && strstr((char *)$rdx, "Hello") != 0
Причина, по которой нам нужны приведение и разыменование на esp
, заключается в том, что $esp
обращается к1147 * зарегистрироваться, и для всех намерений и целей является void*
.При этом регистры здесь хранят непосредственно значения параметров.Таким образом, другой уровень косвенности больше не нужен.
Post
Мне также очень нравится этот вопрос, поэтому я перевел сообщение Энтони на китайский язык и добавил свой ответ в качестве дополнения.Пост можно найти здесь .Спасибо за разрешение @ anthony-arnold.