1. Си / Говнокод #22294

    −46

    1. 1
    Посоны, как заставить Watcom нормально адресовать? Он тупо пихает адреса, относительно нуля, а надо, что относительно сегментных регистров

    Запостил: dm_fomenok, 07 Февраля 2017

    Комментарии (114) RSS

    • показать все, что скрытоinclude gcc
      Ответить
    • Фомёнок, я знаю кто пишет с тебя и ещё нескольких мультиакков...
      Могу ли я сделать деанон, или ты предпочитаешь оставить это под завесой тайны?
      Ответить
    • Покажите пример кода, демонстрирующий такое поведение.
      Ответить
      • char* value = "I am string";
        Долбаный линкер заменяет адрес строки на константу, а не отступ от регистра ds.
        Ответить
        • The following examples illustrate the use of the __far keyword. The examples assume that the small memory model (small code, small data) is being used.
          int __far * ptr;
          declares ptr to be a pointer to an integer. The object ptr is near (addressable using only 16 bits), but the value of the pointer is the address of an integer which is far, and so the pointer contains 32 bits.
          int * __far fptr;
          also declares fptr to be a pointer to an integer. However, the object fptr is far, but the integer that it points to is near.
          int __far * __far ffptr;
          declares ffptr to be a pointer (which is far) to an integer (which is far).

          Это для моделей с 16-битными смещениями. Для редко используемой сегментной модели с 32-битными смещениями будет аналогично.
          Ответить
          • Щито за бусурманская мова? Что мне делать то?
            Ответить
            • Вчити мову.

              Попробовать объявить следующие переменные и посмотреть, что сгенерируется в итоге:
              char       * __far value1 = "I am string";
              char __far *       value2 = "I am string too";
              char __far * __far value3 = "I am another string";


              По идее первый пример запрещает размещать константу в другом сегменте, хотя кладёт в указатель полный адрес (сегмент + смещение). Второй пример разрешает размещать константу где угодно, но в указатель кладёт только смещение относительно сегмента. Третий пример разрешает размещать константу где угодно и в указатель кладёт полный адрес (сегмент + смещение).
              Ответить
              • Выхлоп 16-битного Ватнокома и дизассемблера:
                .387
                		PUBLIC	_value2
                		PUBLIC	_value1
                		PUBLIC	_value3
                DGROUP		GROUP	CONST,CONST2,_DATA
                _TEXT		SEGMENT	BYTE PUBLIC USE16 'CODE'
                _TEXT		ENDS
                CONST		SEGMENT	WORD PUBLIC USE16 'DATA'
                L$1:
                    DB	49H, 20H, 61H, 6dH, 20H, 73H, 74H, 72H
                    DB	69H, 6eH, 67H, 0
                L$2:
                    DB	49H, 20H, 61H, 6dH, 20H, 73H, 74H, 72H
                    DB	69H, 6eH, 67H, 20H, 74H, 6fH, 6fH, 0
                L$3:
                    DB	49H, 20H, 61H, 6dH, 20H, 61H, 6eH, 6fH
                    DB	74H, 68H, 65H, 72H, 20H, 73H, 74H, 72H
                    DB	69H, 6eH, 67H, 0
                
                CONST		ENDS
                CONST2		SEGMENT	WORD PUBLIC USE16 'DATA'
                CONST2		ENDS
                _DATA		SEGMENT	WORD PUBLIC USE16 'DATA'
                _value2:
                    DD	DGROUP:L$2
                
                _DATA		ENDS
                `013_DATA`		SEGMENT	PARA PRIVATE USE16 'FAR_DATA'
                _value1:
                    DW	offset DGROUP:L$1
                _value3:
                    DD	DGROUP:L$3
                
                `013_DATA`		ENDS
                		END


                Итого: указатели value2 и value3 (соответственно типа char __far * и char __far * __far) хранят полный адрес (сегмент и смещение), указатель value1 хранит только смещение относительно DS.

                Переменные value1 и value2 (соответственно типов char * __far и char __far * __far) лежат в отдельном сегменте (не в общем сегменте данных).

                Теперь ясно, на что влияет модификатор __far до звёздочки и после звёздочки.

                Итого: тип (char __near *) хранит только смещение, тип (char __far *) хранит смещение и сегмент.
                Ответить
                • Ты мне предлагаешь везде писать __far*? Есть ли лучший способ?
                  Ответить
                  • Я предлагаю выбрать модель памяти с помощью ключей компилятора -ms, -mc, -mm, -ml, -mh для 16-битного и -mf, -ms, -mc, -mm, -ml для 32-битного (если компиляция из командной строки) или через свойства проекта (если компиляция из IDE).

                    В модели Small (16 бит) и Flat (32 бита) все указатели по умолчанию имеют тип __near.

                    В моделях Compact, Large и Huge указатели на данные по умолчанию __far, если явно не указано __near (т. е. в этих моделях __far для указателя на строку писать не обязательно).

                    В моделях Medium, Large и Huge указатели на функции по умолчанию имеют тип __far.

                    Huge означает почти то же самое, что и Large, но там компилятор вставляет дополнительные хаки для массивов, превышающих размер сегмента.
                    Ответить
                    • Ну выставил я -ml. И нихуя до сих пор нужно писать fixaddr("My little pony string") где fixaddr:
                      #define fixaddr(value) (value + xxx)
                      Ответить
          • чета я не понял. на дворе какой год?
            Ответить
        • > Долбаный линкер заменяет адрес строки на константу, а не отступ от регистра ds.

          пример кода?

          ЗЫ адреса в сегменте данных - они всегда константами были. и даже сейчас очень часто они константы. вопрос - относительно чего они константы. примерчик/демонстрация бы не помешала.
          Ответить
          • Я добиваюсь того что бы линкер адресовал логическими адресами. Из за этого я страдаю потому что надо писать так: fixaddr("My little pony string") где fixaddr:
            #define fixaddr(value) (value + xxx). Если этого не писать то прога будет обращаться по адресу куда захотел линкер(он по факту вставляет физический)
            Ответить
            • показать все, что скрытоТак тебе position independent код поди надо, а не эти изъёбства с сегментами?

              З.Ы. Или ты реально под 16 бит кодишь?
              Ответить
              • Реально 16 бит
                Ответить
              • Я уже сам не знаю чего я хочу, меня задолбал этот макрос!
                Ответить
                • показать все, что скрытоКак ты видишь, что там "константа", а не "отступ от регистра ds"? В x86 не бывает обращений к памяти без участия сегментных регистров (если ты в асме не видишь ds: - это не значит, что его там нет).
                  Ответить
                  • Я написал println(i2s("String")) (i2s перевод числа в строку) и мне вывело 720, а должно было больше 32 000
                    Ответить
                    • показать все, что скрытоprintln(i2s(sizeof(char*))); и println(i2s(sizeof(int))); сколько показывают?

                      Боюсь, что у тебя просто int недостаточно разработан, чтобы вместить указатель.
                      Ответить
                      • Ещё раз проверил,
                        println(i2s(sizeof(char*)));
                        println(i2s((int)"String"));

                        Выводит 4 и 2394
                        Ответить
                        • показать все, что скрытоА sizeof(int) == 2 и sizeof(long) == 4? Тогда тебе надо через unsigned long указатели разглядывать, а не через int.

                          З.Ы. В идеале - через uintptr_t. Но не факт, что он там есть...
                          Ответить
                          • >> А sizeof(int) == 2 и sizeof(long) == 4?
                            Подтверждаю, это так.
                            > > Тогда тебе надо через unsigned long указатели разглядывать, а не через int.
                            Я их вообще не разглядываю. Я тупо передаю char* в println. Если предварительно не пофиксить через fixaddr литерал, то он указывает не куда то там, А НА АДРЕС С ДАННЫМИ В БИНАРНИКЕ. Из за этого нужно прибавлять констунту, равну адресу точки входа в приложение (_cstart)
                            Ответить
                            • показать все, что скрытоОк, тогда давай глянем на твои "test" и fixaddr("test") через каст в unsingned long или uintptr_t (а не через маленький int). А то я пока в суть проблемы не въехал.
                              Ответить
                            • показать все, что скрытоК слову, а под какой операционкой ты этот бинарник запускаешь? И в каком формате бинарник?
                              Ответить
                            • Кстати, режим какой: реальный (DOS) или защищённый (Win16, например)?
                              Ответить
                            • показать все, что скрытоЗ.Ы. А ещё - println это же какая-то самодельная функция? Можно исходник глянуть?
                              Ответить
                              • Настало время рассказать ситуацию. Поспорил на штукарь, что напишу на сишке аналог DOS.

                                Режим реальный, 16 бит, всё по харду.

                                Вся эта поебень стоит в MBR.

                                jmp kernel
                                db 495 dup(0)
                                db 55h, 0AAh
                                db 0
                                kernel:
                                mov sp,07C00h

                                А теперь даю код print:

                                void print(char* value)
                                {
                                int i = 0;
                                while (value[i]) putch(value[i++]);
                                }

                                println тоже самое, только с выводом перевода строки
                                Ответить
                                • показать все, что скрыто> хуёвый загрузчик бинаря
                                  Ч.т.д. Предчувствия меня не обманули.
                                  Ответить
                                • показать все, что скрытоВ какой формат бинаря конпелируешь сишечку? COM, EXE (MZ) или что-то своё?
                                  Ответить
                                  • raw binary
                                    Ответить
                                    • показать все, что скрыто> raw binary
                                      Кто бы знал, что это значит с точки зрения ватнокома...

                                      Если просто линковка под конкретный адрес - должна быть какая-то опция, которой этот адрес задаётся. Плюс ты должен поместить образ в правильное место в памяти и правильно настроить как минимум cs и ds. Как - х.з., я ватноком ни разу не юзал.
                                      Ответить
                                      • У ватнокомовского линкера для raw binary есть единственная опция... как раз адрес старта.
                                        Ответить
                                        • Да, есть. Через неё задаётся имя метода для входа (_cstart)
                                          Есть ещё вторая опция -- offset, через него я пытался фиксить адреса, но линкер не даёт мне 7C00h, он округляет до 8000h
                                          Ответить
                                          • показать все, что скрыто> округляет до 8000h
                                            Значит так и надо. Не спорь с ним. Забей лишнее место нулями, к примеру. Или на асме подвинь/прочитай образ куда надо.

                                            Остаётся узнать, что надо положить в cs и ds чтобы всё заработало...
                                            Ответить
                                            • Нет, так не надо. Я не собираюсь никуда двигать образ. Я собираюсь заставить компилятор использовать ds
                                              Ответить
                                              • показать все, что скрыто
                                                Ответить
                                                • Но хрен там, в дизассемблированном коде значения у jmp меняются в зависимости от переданного дизассемблеру виртуального адреса, а у указателей на строки -- нет. Это что ль дизассемблер кривой? HDasm
                                                  Ответить
                                                  • показать все, что скрытоПокажи хоть строчки дизасма, в которых указатель не меняется. Там исходные байты же показаны, надеюсь. Или только команды?
                                                    Ответить
                                                    • Нет, только команды.

                                                      00000229: 8CD2 mov dx, ss
                                                      0000022B: B8F284 mov ax, 84F2
                                                      0000022E: 89C3 mov bx, ax
                                                      00000230: 89D8 mov ax, bx
                                                      00000232: 0E push cs
                                                      00000233: E87205 call 000007A8

                                                      Вызов вывода строки с адресом 84F2 -- адрес после прибавления к нему 7C00 макросом.
                                                      Ответить
                                                      • показать все, что скрыто> адрес после прибавления к нему 7C00 макросом

                                                        Кстати, а ты ds куда настраиваешь в стартовом коде на асме? И настраиваешь ли вообще?

                                                        > push cs
                                                        > call
                                                        Лол, байты экономят что ли? :) Чтобы функция думала, что её вызвали как far?
                                                        Ответить
                                                        • Да. Довольно частый приём в сегментированной модели. Многие компиляторы так делали.

                                                          P.S. Это не только экономия байтов, но и экономия на фиксапах. Если сделать call near, то адрес будет относительный и фиксап делать не нужно, если функция в том же сегменте. А в стеке будет такое же смещение, как и при call far.
                                                          Ответить
                                                        • В общем, у x86 всё красиво:

                                                          1. В байтовом представлении инструкций JMP SHORT (включая условные переходы), JMP NEAR, CALL NEAR хардкодится смещение не относительно сегмента, а относительно конца данной инструкции. Т. е. опкод JMP SHORT/NEAR, за которым следует ноль, эквивалентен бесполезной инструкции NOP. Адреса коротких переходов патчить не нужно.

                                                          2. В байтовом представлении JMP FAR, CALL FAR хардкодится смещение относительно сегмента. В экзешнике после загрузки придётся патчить адреса, если он загружен по адресу, отличающемуся от дефолтного.

                                                          3. И после вызова CALL NEAR, и после вызова CALL FAR в стеке будет смещение относительно сегмента, хотя в байтовом представлении CALL NEAR хардкодится относительный адрес.

                                                          4. Для инструкций, работающих с данными, (типа MOV) аналога с относительными адресами нет. Адреса данных всегда хардкодятся от начала сегмента. Если экзешник загружен по адресу, отличающемуся от дефолтного, их нужно патчить.
                                                          Ответить
                                                          • показать все, что скрыто>> их нужно патчить.

                                                            именно потому большинство линкёров в современных ОС умеют PIC. Там в заголовке даны адреса для "подвигать'
                                                            Ответить
                                                            • Так PIC как раз делают, чтобы как можно меньше патчить. Один раз посчитали разницу между фактическим адресом и ожидаемым и прибавляем. Но совместимость с PIC нужно предусматривать не на этапе линковки, а ещё на этапе компиляции.

                                                              А без PIC вместе с экзешником нужно хранить таблицу релокейшнов (фиксапов), которую загрузчик при старте экзешника должен пробегать и патчить по ней образ свежезагруженного экзешника.
                                                              Ответить
                                                          • показать все, что скрытоА в amd64 и ip-relative завезли.
                                                            Ответить
                                                            • показать все, что скрытовсмысли все инструкции теперь отсчитываются от регистра ip? даже дата?
                                                              Ответить
                                                              • In 64-bit mode, addressing relative to the contents of the 64-bit instruction pointer (program counter)—called RIP-relative addressing or PC-relative addressing—is implemented for certain instructions. In such cases, the effective address is formed by adding the displacement to the 64-bit RIP of the next instruction.

                                                                In the legacy x86 architecture, addressing relative to the instruction pointer is available only in controltransfer instructions. In the 64-bit mode, any instruction that uses ModRM addressing can use RIPrelative addressing. This feature is particularly useful for addressing data in position-independent code and for code that addresses global data.

                                                                Without RIP-relative addressing, ModRM instructions address memory relative to zero. With RIPrelative addressing, ModRM instructions can address memory relative to the 64-bit RIP using a signed 32-bit displacement. This provides an offset range of ±2 Gbytes from the RIP.

                                                                Programs usually have many references to data, especially global data, that are not register-based. To load such a program, the loader typically selects a location for the program in memory and then adjusts program references to global data based on the load location. RIP-relative addressing of data makes this adjustment unnecessary.
                                                                Ответить
                                                  • Особенность x86. У JMP адреса в бинарнике относительные (относительно текущей инструкции, если не FAR), у данных — абсолютные.
                                                    Ответить
                                              • показать все, что скрыто> Я не собираюсь никуда двигать образ.
                                                Блять, ну вот как мы тебе должны советовать, если не видим ни образа, ни исходников... Это ж ёбаная нейрохирургия по скайпу.
                                                Ответить
                                          • Префикс шестнадцатеричной системы (0x) был указан? Без него линкер считает, что offset указан в десятичной.
                                            Ответить
                                        • "У ватнокомовского линкера для raw binary есть единственная опция... как раз адрес старта."

                                          так это же стандартное встроенное счастье!

                                          ядро так можно (и часто нужно) компилить. но динамически загружаемые модуля - нет.

                                          вообщем, фоменОку надо начинать учить форматы exe-шников и учиться писать загрузчик оных. а потом просто проги компилить в тот формат, и их грузить для исполнения.
                                          Ответить
                  • показать все, что скрытоVanished
                    Ответить
            • это не линкер. линкер такое делать не умеет. в опции компелятора смотрись.

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

              вообщем, тебе не хватает нечто типа `ASSUME DS:DSEG`.
              Ответить
      • показать все, что скрытоЕсть подозрение, что у него релоки почему-то не применились (хуёвый загрузчик бинаря? таблица с релоками куда-то проебалась?). Из-за этого в сегментной части far адресов остаётся 0 и прога лезет не туда.

        Но надо больше инфы - операционку, формат бинаря и т.п.
        Ответить
        • Придётся либо писать нормальный загрузчик экзешника (который умеет патчить таблицу релоков), либо выбирать модель памяти Small/Tiny и линковать в com-файл (в нём патчить ничего не нужно, но его НУЖНО загружать по фиксированному смещению от начала сегмента).

          Ну ещё как вариант навелосипедить аналог PIC.
          Ответить
        • я не знаю что там ватком делает (и что такое ватком, а конкретнее: кто линкер?) но на новых тулчэйнах его проблема описывалась бы как "кривой линкер скрипт"/"кривой конфиг линкера". потому что если даже PIC/no-PIC в компилере правильно сделаешь, все равно будет монопенисуально если линкер в левый формат выведет.
          Ответить
          • показать все, что скрытоДа надуманная у него проблема. Читал бы "ядро" на 0x8000, как ему предлагает ватноком, и не ебал бы моск себе и нам. Один хер что-то с диска подгружать придётся, ибо в 512 байт "подобие DOS" ну никак не влезет.
            Ответить
            • > > Один хер что-то с диска подгружать придётся, ибо в 512 байт "подобие DOS" ну никак не влезет.

              Хуй там. У меня весь код в одном сишном файле, компилится в один бинарник на 3 КБ. И главное, что работает, если не считать то, что вручную надо фиксить адресацию.

              > > Читал бы "ядро" на 0x8000.
              Не царское это дело читать ядро, это дело для bios. (А с хуя ли он кстати загружает весь бинарь?)
              Ответить
              • Попробуй Ватнокомом линковать в exe, а дальше воспользоваться программой типа exe2bin или exe2com. Может быть, удастся найти процессор exe, который умеет фиксить на произвольный адрес (в том числе на 0x7c00). Какой-то из них точно умеет (ведь чем-то DOS линковали).
                Ответить
              • показать все, что скрытоХ.з. А чё за биос? Игра на грани UB'а, имхо. Емнип, биос больше 512 байт не обязан читать.
                Ответить
                • БИОС читает 512 байт из MBR. А дальше уже MBR читает загрузочный сектор. Если dm_fomenok пишет загрузочный сектор системы, значит, это MBR по какой-то причине читает не один сектор, а весь образ.
                  Ответить
                • Всё, кажется, понял.

                  Загрузчики, отличные от стандартных Windows-загрузчиков, могут использовать всё пространство между MBR и первым разделом (около 32 кБ; 1-й–63-й сектора́) для собственных целей. В таких случаях под MBR понимают весь загрузочный код, а для выделения именно первых 512 байт говорят, что они расположены в MBS (Master Boot Sector) — главном загрузочном секторе.

                  Похоже, что BIOS окончанием MBR считает не окончание первого сектора, а сектор, в конце которого встретилась сигнатура 0x55, 0xAA.
                  Ответить
    • показать все, что скрыто> Watcom

      А што это за хуйня такая, просвети
      Ответить
    • показать все, что скрыто> относительно нуля
      Для flat модели памяти это нормально.
      Ответить
    • показать все, что скрытоЭто для фиксинга RSS нужно?
      Ответить
    • Авторитетно заявляю, что задолбался чинить эту поебень, лучше заплачу штукарь. Или RSS пофикшу
      Ответить

    Добавить комментарий