Как вернуть имя переменной, хранящейся по определенному адресу памяти в С++

Впервые публикую здесь после того, как так много моих результатов Google появилось на этом замечательном сайте.

По сути, я хотел бы найти имя переменной, хранящейся по определенному адресу памяти. У меня есть приложение для редактирования памяти, которое я написал, которое редактирует одно значение, проблема в том, что каждый раз, когда приложение, содержащее это значение, исправляется, мне приходится жестко кодировать новый адрес памяти в моем приложении и перекомпилировать, что занимает так много времени для содержание, что его почти не стоит делать.

Что я хотел бы сделать, так это получить имя переменной, хранящейся по определенному адресу памяти, чтобы затем я мог найти ее адрес во время выполнения и использовать его в качестве адреса памяти для редактирования.

Это все пишется на C++.

Заранее спасибо!

Редактировать:

Ну, я решил, что хочу передавать данные из файла .txt, но я не уверен, как преобразовать строку в LPVOID для использования в качестве адреса памяти в WriteProcessMemory(). Это то, что я пробовал:

    string fileContents;

    ifstream memFile("mem_address.txt");
        getline(memFile, fileContents);
    memFile.close();

    LPVOID memAddress = (LPVOID)fileContents.c_str();

    //Lots of code..

    WriteProcessMemory(WindowsProcessHandle, memAddress, &BytesToBeWrote, sizeof(BytesToBeWrote), &NumBytesWrote);

Код с точки зрения синтаксиса верен, он компилируется и запускается, но возникают ошибки WriteProcessMemory, и я могу только предположить, что это связано с моей неисправной переменной LPVOID. Я извиняюсь, если расширение использования моего вопроса противоречит правилам, я удалю свое редактирование, если это так.


person rrkpp    schedule 23.08.2010    source источник


Ответы (7)


Скомпилируйте и сгенерируйте так называемый map файл. Это можно легко сделать с помощью Visual-C++ (опция компоновщика /MAP). Там вы увидите символы (функции, ...) с их начальным адресом. Используя этот файл карты (внимание: его нужно обновлять каждый раз при перекомпиляции), вы можете сопоставить адреса с именами.

На самом деле это не так просто, потому что адреса относятся к предпочтительному адресу загрузки и, вероятно, будут (рандомизация) отличаться от фактического адреса загрузки.

Некоторые старые советы по получению правильного адреса можно найти здесь: http://home.hiwaay.net/~georgech/WhitePapers/MapFiles/MapFiles.htm

person jdehaan    schedule 23.08.2010
comment
Спасибо за все ваши ответы, в моих обстоятельствах может показаться, что я не смогу выполнить именно то, что задумал, но, по крайней мере, у меня достаточно информации по этому вопросу, чтобы не тратить еще больше времени. размышляя, как я могу это сделать. - person rrkpp; 24.08.2010

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

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

Обратите внимание, что для переменных стека получение их имен потребует полных символов отладки и является очень нетривиальным процессом. Переменные кучи не имеют имен, так что вам там, естественно, не повезет. Кроме того, как упоминалось в ответе @jdehaan, файлы карт могут быть немного сложными для работы в лучшие времена. В общем, лучше всего иметь надлежащий протокол управления, который можно использовать, чтобы вообще избежать какой-либо зависимости от содержимого памяти другой программы.

Наконец, если у вас нет контроля над другой программой, я бы порекомендовал разместить переменную в отдельном файле данных. Таким образом, вам больше не нужно будет каждый раз перекомпилировать, и вы даже сможете поддерживать несколько версий программы, в которую вы втыкаете. У вас также может быть какая-то служба автоматического обновления, которая загружает новые версии этого файла данных с вашего сервера, если хотите.

person bdonlan    schedule 23.08.2010

Если вы на самом деле не являетесь владельцем рассматриваемого приложения, стандартного способа сделать это не существует. Если у вас есть приложение, вы можете подписаться на ответ @jdehaan.

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

person Franci Penov    schedule 23.08.2010
comment
Не могу поверить, что я никогда не думал об этом! Я обязательно посмотрю на синтаксический анализ канала или текстового файла в качестве решения этой проблемы! - person rrkpp; 24.08.2010

Вы не можете сделать это напрямую; имена переменных фактически не существуют в скомпилированном двоичном файле. Вы можете сделать это, если программа написана, скажем, на Java или C#, которые сохраняют информацию о переменных в скомпилированном двоичном файле.

Кроме того, в общем случае это невозможно, потому что всегда возможно, что самая актуальная копия значения внутри целевой программы находится внутри регистра ЦП, а не в памяти. Это более вероятно, если рассматриваемая программа скомпилирована в режиме релиза с включенными оптимизациями.

Если вы можете убедиться, что целевая программа скомпилирована в режиме отладки, вы должны иметь возможность использовать символы отладки, выдаваемые компилятором (файл .pdb), для сопоставления адресов с переменными, но в этом случае вам нужно будет запустить цель процесс, как если бы он отлаживался - простые методы чтения памяти процесса и записи памяти процесса не будут работать.

Наконец, ваш вопрос игнорирует очень важное соображение - не обязательно должна быть переменная, соответствующая конкретному адресу, даже если такая информация хранится.

person Billy ONeal    schedule 23.08.2010
comment
Write/ReadProcessMemory(), безусловно, можно использовать с символами отладки без необходимости запускать приложение особым образом. Как еще VC++ мог бы подключаться к работающим процессам? Конечно, никто никогда не говорил, что интерпретировать отладочные символы легко... :) - person bdonlan; 24.08.2010
comment
@bdonlan: я предполагаю локальные переменные. Вам придется остановить выполнение программы, чтобы увидеть, когда они входят в область действия и выходят из нее. Это требует запуска целевого процесса под отладчиком. - person Billy ONeal; 24.08.2010
comment
Или подключить отладчик постфактум, но на самом деле вам нужно остановить выполнение, чтобы получить стабильное чтение о том, существует ли локальная переменная вообще. - person bdonlan; 24.08.2010

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

typedef struct {
    const char head_tag[15] = "VARIABLE_START";
          char var_name[32];
          int  value;
    const char tail_tag[13] = "VARIABLE_END";
} debuggable_int;

Теперь ваше приложение должно иметь возможность искать программу в пространстве памяти и искать теги начала и конца. Как только он находит одну из ваших отлаживаемых переменных, он может использовать элементы var_name и value для ее идентификации и изменения.

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

person bta    schedule 23.08.2010

Билли О'Нил начал двигаться в правильном направлении, но (ИМХО) не достиг реальной цели. Предполагая, что вашей целью является Windows, гораздо проще было бы использовать функции обработчика символов Windows, в частности SymFromName, которые позволят вам указать имя символа и вернут (среди прочего) адрес для этого символа.

Конечно, для выполнения всего этого вам придется работать под учетной записью, которой разрешено выполнять отладку. Однако, по крайней мере, для глобальных переменных вам не обязательно останавливать целевой процесс, чтобы найти символы, адреса и т. д. На самом деле, для процесса вполне нормально использовать их на себе, если он того пожелает (весьма некоторые из моих ранних экспериментов по изучению этих функций сделали именно это). Вот небольшой демонстрационный код, который я написал много лет назад, и он дает хотя бы общее представление (хотя он достаточно старый, чтобы использовать SymGetSymbolFromName, который на пару поколений отстает от SymFromName). Скомпилируйте его с отладочной информацией и отойдите в сторону — он выдает довольно много вывода.

#define UNICODE
#define _UNICODE
#define DBGHELP_TRANSLATE_TCHAR
#include <windows.h>
#include <imagehlp.h>
#include <iostream>
#include <ctype.h>
#include <iomanip>
#pragma comment(lib, "dbghelp.lib")

int y;

int junk() {
    return 0;
}

struct XXX { 
    int a;
    int b;
} xxx;

BOOL CALLBACK 
sym_handler(wchar_t const *name, ULONG_PTR address, ULONG size, void *) {
    if (name[0] != L'_')
        std::wcout << std::setw(40) << name 
            << std::setw(15) << std::hex << address 
            << std::setw(10) << std::dec << size << L"\n";
    return TRUE;
}

int 
main() {
    char const *names[] = { "y", "xxx"};

    IMAGEHLP_SYMBOL info;

    SymInitializeW(GetCurrentProcess(), NULL, TRUE);

    SymSetOptions(SYMOPT_UNDNAME);

    SymEnumerateSymbolsW(GetCurrentProcess(), 
        (ULONG64)GetModuleHandle(NULL),
        sym_handler,
        NULL);

    info.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);

    for (int i=0; i<sizeof(names)/sizeof(names[0]); i++) {
        if ( !SymGetSymFromName(GetCurrentProcess(), names[i], &info)) {
            std::wcerr << L"Couldn't find symbol 'y'";
            return 1;
        }

        std::wcout << names[i] << L" is at: " << std::hex << info.Address << L"\n";
    }

    SymCleanup(GetCurrentProcess());
    return 0;
}
person Jerry Coffin    schedule 23.08.2010
comment
Вероятно, команда WinDbg !ln реализована с использованием чего-то похожего внутри. - person Chubsdad; 24.08.2010
comment
@chubsdad: Да, я думаю, что да (с той очевидной разницей, что он передает какой-то другой процесс вместо GetCurrentProcess(), как я сделал выше...) - person Jerry Coffin; 24.08.2010

В WinDBG есть особенно полезная команда

ln

здесь

Учитывая ячейку памяти, он даст имя символа в этой ячейке. С правильной отладочной информацией это благо для отладчика (я имею в виду человека, занимающегося отладкой:))!

Вот пример вывода в моей системе (XP SP3)

0:000> ln 7c90e514 (7c90e514)
ntdll!KiFastSystemCallRet | (7c90e520) ntdll!KiIntSystemCall Точные совпадения: ntdll!KiFastSystemCallRet ()

person Chubsdad    schedule 24.08.2010