Статья Память и адреса компьютера

  • 681
  • 318
Сегодня я расскажу, как устроена память компьютера, об адресах, и почему мы к ним обращаемся, когда хотим прочитать/записать какие-то данные.

Всего на данный момент насчитывается по крайней мере 5 типов памяти.

1. Регистровая память - Память находящаяся непосредственно в процессоре, является самой быстрой в компьютере с временем доступа менее 0,3 наносекунды(0,0000000003 секунды), но объем данной памяти ничтожно мал, я не нашёл точной информации о размере регистров в современных процессорах, но один из источников пишет до 4кб(4096 байт). Из названия можно понять, что в данной памяти хранятся непосредственно регистры(команды), которые процессор выполняет. О регистрах можно узнать здесь(клик).

2. Кэш память(L1/L2/L3) - Данная память также находится в процессоре, и фактически кэш не отличается от оперативной памяти, разница только в её скорости и объеме. Всего существует 3 уровня кэш памяти, и чем больше уровень тем медленнее память, но объемнее.
Время доступа к кэш памяти первого уровня менее 1 наносекунды с объемом до 1 мегабайта, ко второму уровню время доступа более 1нс и может достигать 10нс с объемом до 8мб, доступ к третьему уровню в районе 5-20нс с объемом вплоть до 128мб.

3. Оперативная память - Память в которой хранится программа и её временные данные, которые процессор будет выполнять, она имеет среднюю скорость доступа(около 30-100нс), объем такой памяти в среднем 4-16гб, но для серверного оборудования есть плашки вплоть до 128гб.
Также существует оперативная память для видео-чипов, которая называется GDDR, она ничем не отличается, но данная память быстрее, потому-что видео-чипу, как и процессору требуется достаточно быстрый доступ к нужным данным. В данный момент самая современная оперативная память для видео-чипов это GDDR6X.
Интересный факт, в Xbox Series X/S используется оперативная память GDDR6 объемом 16/10(по официальным данным, играм доступно лишь 13 в X версии) гигабайт, которая сразу используется, как оперативная и видео память. То есть, если игре потребуется 7гб оперативной памяти, то в качестве видео памяти ей останется 6гб.

4. Внешняя память(SSD/HDD) - Хранит всю программу и её файлы. В несколько тысяч раз медленнее(скорость доступа 1-20 миллисекунд), но с огромным объемом, вплоть до более чем 8тб(8 192гб).

5. Третичная память - Очень медленная со скоростью доступа до нескольких секунд, но самая объемная. В настольных компьютерах давно не используется. Больше информации о ней здесь(клик).

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


1640313055029.png

1640313129063.png
Адрес в памяти - Место находящейся информации в памяти(вида HEX), аналогично тому, по какому вы адресу живёте, чтобы вас найти кому-либо(например друзьям, но в данном случае процессору). Процессор по адресам может находить требуемые данные для их выполнения, например целочисленные числа или функции. Именно поэтому в гейм хакинге мы так много работаем с памятью. Адрес присваивается каждому байту, который в свою очередь является 8 битами, поэтому создавая bool'евую переменную она будет иметь размер 1 байт, хотя и хранение всех 8 битов из которых используется только самый младший(правый) бит не выгодно, потому-что хранится лишь логический ноль или один, в данном случае можно лишь прибегнуть к использованию побитовому вектору, что не удобно, и вам нужно будет везде расставлять комментарии, чтобы не запутаться(как с массивами). Стандартная int переменная имеет размер 4 байта(32 бита), поэтому компьютеру нужно будет прочитать байты по четырём адресам.
1640357603487.png


Есть несколько типов адресов в виртуальной памяти - Статические и динамические.

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

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

Смещение или Offset - Относительный адрес элемента объекта, требуется для доступа к какому-либо элементу определенного объекта из класса/структуры или указателя, например имени игрока, поэтому мы берём "Игрок" + "Имя игрока" и получаем конечный адрес с информацией о имени конкретного игрока.
Пример C++ кода программы
C++:
struct S_Player {
    // Constructor            // 0x00 -> 0x00
    S_Player( ) { i_health = 100, i_armor = 0, s_name = "Local", i_id = 0; };
    // Destructor             // 0x00 -> 0x04
    ~S_Player( ) { };
 
    int             i_health; // 0x04
    int             i_armor;  // 0x08
    std::string s_name;       // 0x0C
    int             i_id;     // 0x1C
}; // Адреса в структурах и классах последовательны, так что нам не составит труда их подсчитать, стандартная строка занимает 16 байт.
   // Конструктор и деструктор в данном случае VTable функции по адресу 0x00, которые имеют свой относительный адрес(как показано).
...
S_Player g_local_player; // Условный 0x0505A05F адрес, потому-что мы на самом деле не можем узнать его, пока не выясним дебагером.
...
std::cout << g_local_player.i_health << std::endl;
Пример C++ кода чит-программы(обращаемся к броне игрока)
C++:
// Получаем базовый адрес программы/модуля, чтобы мы могли обращаться к адресам в этой программе/модуле.
// Можно использовать NULL, если получаем адрес текущего модуля или исполняемого файла программы, если модуль извне, то пишем его название, например, L"client.dll".
uintptr_t base = ( uintptr_t )GetModuleHandle( NULL );
// Создаем указатель на адрес нашего игрока, и разыменовываем для получения его данных(адреса в данном случае).
uintptr_t local_player = *reinterpret_cast< uintptr_t* >( base + 0x0505A05F );
// Создаем указатель на адрес брони нашего игрока, и разыменовываем для получения его данных.
int local_armor = *reinterpret_cast< int* >( local_player + 0x08 );
// Выводим количество брони игрока.
std::cout << local_armor << std::endl;

Иерархия адресов в некоторых программах(конкретнее в играх) может быть достаточно большой, как в движке Uneal Engine 4, например, чтобы получить локального игрока нам придется выяснить следующие адреса, и пройтись по ним:
ModuleBase + UObjectBase -> UObjectBaseUtility -> UObject -> UPlayer -> ULocalPlayer
1640318125407.png


Более подробно об адресах и памяти вы можете узнать из данного ролика:
 
Последнее редактирование:
Сверху Снизу