Ответы пользователя по тегу Assembler
  • Линейный аддресc? Умножение на 16, как это работает вообще и за чем в модели памяти, 32 бит, с 32 бит регистрами?

    @Mercury13
    Программист на «си с крестами» и не только
    У нас маленькие регистры и много памяти. То есть полный адрес задаётся регистровой парой, а не одним регистром. Можно сделать линейную адресацию, а можно сегментную. Объясню логику за сегментами.
    1. У нас линейная адресация, и надо по какой-то причине прибавить единицу к адресу. Если нижний адрес переполнится, надо переносить эту единицу в верхний — то есть делать длинный сумматор, увеличивать регистровый файл, постоянно работать с парами регистров и эту пару никак нельзя заменить одним — машина по сути превращается в костыльную 32-битную.
    2. Линейная адресация сильно усложняет загрузчики — им приходится обрабатывать так называемые relocations. Задан базовый адрес, и если загрузка случилась по другому. к определённым точкам в памяти надо прибавить разницу. В сегментной адресации этих relocations куда меньше: они задаются по сегментам, а не по точкам. (А в x64 для избавления от relocations ОЧЕНЬ МНОГО команд используют адресацию «от IP».)
    3. Совместимость с Intel 8080. Он тогда широко применялся, и IBM пошла на хитрость: после небольшой переделки проги под 8080 работали и на их ПК. Сейчас COM — странная технология Windows, а раньше COM — это было расширение маленьких исполняемых файлов в один сегмент длиной.
    Ответ написан
  • Почему в аргумент функции передается стэк?

    @Mercury13
    Программист на «си с крестами» и не только
    Итак, на стеке лежит локальный объект, и так совпало, что его адрес совпадает с регистром esp. Стек на x86 растёт вниз, так что такое бывает.
    Для этого объекта мы вызываем функцию с соглашением вызова cdecl thiscall и одним dword-параметром, предположительно указателем. Функция возвращает указатель/ссылку на объект — видимо для «текучего интерфейса».

    Как я понял, вы подставным DLL или подобной дрянью хотите вызвать эту функцию, верно? Тогда в DLL пишем примерно такое.
    #include <iostream>
    
    class X
    {
    public:
        // Наделай полей — непонятно, какой нужен размер объекта, но минимум 7 dword
        uint32_t field0 = 0, field4 = 0, field8 = 0, fieldC = 0,
               field10 = 0, field14 = 0, field18 = 0;
        // Просто тестовая функция, тебе не нужна
        X& doSmth(void* param);
    };
    
    
    // Просто тестовая функция, тебе не нужна
    X& X::doSmth(void* param)
    {
        std::cout << "My address is " << this << std::endl;
        std::cout << "My param is " << param << std::endl;
        return *this;
    }
    
    // Известно, что параметр — указатель/ссылка, но какого типа — неизвестно.
    // Пусть будет void*.
    using PFunc = X& (X::*)(void*);
    
    int main()
    {    
        PFunc func = &X::doSmth;  // Тебе надо reinterpret_cast<PFunc>(0x471440)
        X x;
        auto param = reinterpret_cast<void*>(0x5678);  // Или придумай, чему должен равняться этот param
        (x.*func)(param);
        return 0;
    }
    Ответ написан
    Комментировать
  • Почему atoi возвращает в EAX 0, хотя строка подается с символа цифры?

    @Mercury13
    Программист на «си с крестами» и не только
    Я не знаю, что такое month — но, по всей видимости, этот макрос надо вызывать invoke atoi, month.

    Потому что [month] — это разыменование. А нам адрес нужен.

    Так возвращает ноль или вылетает, я не понял? Если случился вылет, в eax может быть что угодно.

    С lea edi, [month] всё верно — lea не разыменовывает, а грузит адрес. Хотя ассемблер, по идее, должен выдать обычный mov edi, month.
    Ответ написан
    Комментировать
  • Убрать указание размера функции?

    @Mercury13
    Программист на «си с крестами» и не только
    Зависит от того, чем линкуете.
    Если вызываете напрямую ld — --kill-at.
    Если gcc — то -Wl,--kill-at вроде.
    Ответ написан
  • Может ли процессор изменять порядок инструкций в программе?

    @Mercury13
    Программист на «си с крестами» и не только
    А там нет такого уж сложного анализа. Процессор преобразует каждую команду x86 в команду VLIW (Very Long Instruction Word — что делать каждому из блоков процессора), а затем пробует спрессовывать эти команды вместе. Насколько мне известно, первым на x86 такой механизм был предложен в процессоре Transmeta Crusoe, за пару лет до Intel.

    А ещё подобное налаживание зависимостей позволяет процессору делать что-то сразу же, а не ждать данных из медленной памяти. А ещё — патчить ошибки процессора :). Не помню, запатчили так Meltdown или нет.

    Подсистема памяти тоже может так работать — на x86 я не могу привести примеры, но представьте себе: одну строку кэша сбросило в память, а вторую держит.
    Ответ написан
    2 комментария
  • Обьясните кто то про регистры в Ассемблере?

    @Mercury13
    Программист на «си с крестами» и не только
    У меня нет линукса, чтобы проверить, но, кажется, программа в корне неверна. Вы используете 32-битное соглашение вызова на 64-битной машине.
    https://www.informatik.htw-dresden.de/~beck/ASM/sy...
    https://blog.rchapman.org/posts/Linux_System_Call_...

    Откуда 32-битный вызов на x64 — дайте пруфлинк, может, в вашем линуксе так и есть.

    Регистры — это маленькие и очень быстрые ячейки памяти, встроенные в процессор. Их ограниченное количество, и у них конкретные чётко зафиксированные названия.

    Часто для работы с данными малых разрядностей и совместимости с ранним кодом процессор даёт доступ и к половинкам регистров. Так, нижняя половина регистра rax — eax, нижняя четверть — ax, половины ax (соответственно восьмушки rax) называются al и ah. Сами понимаете: когда процессор был 16 бит, регистр назывался ax = al + ah. Сделали 32 бита — стал eax. Сделали 64 бита — стал rax.

    Для вызова системных функций Linux используется такое соглашение вызова. Все функции висят на прерывании 0x80, rax — название функции, остальные параметры рассовываются по регистрам.

    Насчёт int, char, double. Знаковость (signed/unsigned) определяется инструкциями ассемблера (например, ja = jump if above для unsigned, jg = jump if greater для signed). Длина — задействованным куском регистра (rax = long long, eax = int/long, ax = short, al = char). С дробными числами работает отдельный блок процессора, т.н. сопроцессор, со своими регистрами (в первых x86 он был отдельной микросхемой, существовавшей не во всех компьютерах, отсюда название).
    Ответ написан
    4 комментария
  • Из-за чего ошибка чтения?

    @Mercury13
    Программист на «си с крестами» и не только
    Вы компилируете функцию в двоичный код и на месте же вызываете. Так что есть вопросы.
    1. Функции обеспечили правильное соглашение вызова?
    Мне что-то кажется, что при подобной ручной компиляции проще работать с соглашением PASCAL или STDCALL — ну, шут его знает.
    2. VirtualProtect работает с целыми страницами. Так что на куске памяти из сегмента данных (ну или из стека, полного кода не вижу) она в лучшем случае откажет, в худшем ничего не сделает. Проверьте, была ли ошибка.
    3. Первый параметр должен быть source, а не &source.
    Ответ написан
    Комментировать
  • Как запустить .EXE создаваемый Турбо Паскалем?

    @Mercury13
    Программист на «си с крестами» и не только
    Можно после запуска нажать Alt-F5, чтобы увидеть, что программа вывела.
    Есть там эта кнопка в меню — пункт не помню, а кнопку помню.
    UPD. Команда называется «User screen».
    Ответ написан
    Комментировать
  • Что означает "+" после регистра?

    @Mercury13
    Программист на «си с крестами» и не только
    Перед нами синтаксис ассемблера Intel.
    qword ptr [] — адрес восьмибайтовой переменной в памяти. Если конкретный — прямая адресация, если внутри какой-то регистр — косвенный.
    + — простая операция сложения.

    Перед нами косвенная адресация со смещением. То есть, берём содержимое регистра eax, прибавляем 80h, обращаемся по этому адресу и то, что получилось, загоняем в стек сопроцессора как double.

    ЗЫ. Подобный полный показ команд с ведущими нулями и без указателя 16-й системы (80h) обычно используется в дизассемблерных листингах.
    Ответ написан
    3 комментария
  • Как процессор работает с памятью?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Для косвенной адресации. Мы не просто обращаемся к 325-й ячейке, а проводим вычисление, получаем 325 и обращаемся к ячейке с этим номером. Получили бы 456 — обратились бы к 456-й.
    Это даёт кучу интересных структур данных, и самая простая из них — массив.

    Многоуровневый указатель — это уже дело более высокого уровня. Пока указателем я называл просто адрес в памяти. Но, извините, в памяти хранятся однобайтовые целые, двухбайтовые целые, дробные, массивы, строчки, другие указатели — в общем, на более высоком уровне появляется понятие «тип данных». И указатели бывают на целое, на дробное — и на указатель!

    2. Разница между каким-то адресом и началом структуры данных. Например, у нас есть такая структура данных (для простоты без выравнивания).
    a : word
    b : dword
    c : byte

    Тогда поле c находится по смещению 2+4 = 6, и если голова структуры по адресу 124, то поле c будет по адресу 124+6=130.

    Также в стандартном режиме 8086 — 16-битный процессор с 1 мегабайтом адресуемой памяти — была хитрая система адресации под названием «сегмент:смещение», и адрес вычислялся по формуле сегмент·16 + смещение. При этом было принято считать, что сегмент незыблем, а по структуре данных (которая при этом не могла превышать 64K) мы двигались, меняя смещение.

    3. Т.н. «переключение контекста» — ядро ОС просто сохраняет регистры в памяти. Очень долгая задача, кстати.
    Ответ написан
    Комментировать
  • Как в C++ распределяется память?

    @Mercury13
    Программист на «си с крестами» и не только
    Это называется фрагментация памяти. Некоторые «мусорщики» (как в Java или C#) способны сдвинуть объекты и получить большой непрерывный отрезок.
    Менеджер памяти C++ дефрагментировать память не способен и выдаст нехватку памяти.
    В любом случае, массив — что в Java, что в C++ — будет занимать непрерывный отрезок памяти.

    Чтобы совмещать мусорные языки с системными интерфейсами, есть способы пометить структуру как неперемещаемую. Но это уже на стыке виртуальной машины Java/C# и системного кода, который пишется на Си(++).
    Ответ написан
    9 комментариев
  • Как разделить два числа в Assembler при помощи цикла?

    @Mercury13
    Программист на «си с крестами» и не только
    Хотелось бы уточнить.
    1. Размеры делимого и делителя.
    2. Знаковые или беззнаковые?
    3. Нужен ли остаток?
    4. Делим на константу или на что-то заранее неизвестное?

    Вот для примера 16-битное беззнаковое делить на произвольное 8-битное беззнаковое, как я понял, без остатка.
    www.avr-asm-tutorial.net/avr_en/calc/DIV8E.html

    А вот так работает деление на константу (в данном случае на 6).
    stackoverflow.com/questions/34136339/how-does-divi...
    Ответ написан
  • Возможно ли создать "универсальный" драйвер для исполнения произвольного кода в Ring0?

    @Mercury13
    Программист на «си с крестами» и не только
    Это к Sony или StarForce. И те, и другие делали заSHITу и прокалывались на подобном.
    Ответ написан