Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Страница 1 из 2 12 ПоследняяПоследняя
Показано с 1 по 10 из 16
  1. #1
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,767
    Репутация:
    2256 ±

    Баг в GetPVarString / GetSVarString и как его исправить

    Всем привет.
    Начну с того, что это не совсем обычный урок - скорее, повествование. Но нигде на pro-pawn я не видел статей подобного жанра, так что...


    Всё началось с того, что Untonyst, проверяя работу своего инклуда fix_K2BEx.inc, наткнулся на странный баг: при использовании функции BanEx в samp.ban вместо текста бана записывалась какая-то "каша" из букв.
    Об этом он вскоре написал мне и, как оказалось, баг наблюдался и в моём инклуде dc_kickfix.inc, поэтому я решил разобраться.

    Для начала я взял тестовый мод, в котором использовался инклуд dc_kickfix.inc, и ввёл в нём команду "/ban 0 проверка":

    А вот содержимое файла samp.ban после бана:

    Пример кода, с помощью которого можно воспроизвести этот баг:
    PHP код:
        SetPVarString(playerid"ban_reason""проверка");
        new 
    reason[128];
        
    GetPVarString(playerid"ban_reason"reasonsizeof(reason));
        
    DeletePVar(playerid"ban_reason");
        
    BanEx(playeridreason); 
    Поскольку задачей инклудов fix_K2BEx.inc и dc_kickfix.inc было сделать кик/бан с задержкой, чтобы успеть показать игроку причину, функции Kick, Ban и BanEx вызывались с помощью таймера.
    А чтобы как-то передать функции BanEx строку с причиной бана (в функции SetTimerEx спецификатор "s" не работает), эта строка предварительно сохранялась в PVar.
    Можно было бы сделать сохранение в одномерном массиве, но если забанить 2 игроков за полсекунды, то последний игрок перезапишет причину бана первого (массив-то общий!) и первому игроку будет показана та же причина бана, что и последнему.
    Если же сделать двухмерный массив, то его придётся делать размером MAX_PLAYERS * 128 ячеек (вместо 128 может быть и меньшее число - главное, чтобы вместилась причина бана), но резервировать участок памяти сразу под всех игроков и никогда не высвобождать его тоже не целесообразно.
    Именно поэтому было решено использовать PVar'ы - с ними память выделяется только тогда, когда это нужно, а после использования её можно высвободить, удалив PVar.

    Со слов Untonyst, баг проявлялся только в тех случаях, когда в причине бана были русские буквы, и если в коде выше поставить "print(reason);" перед вызовом BanEx, то причина бана с символами кириллицы будет выведена, как ни в чём не бывало, но в samp.ban текст запишется неправильно. Очень странно.
    С его же слов, если вместо хранения причины в PVar сделать двухмерный массив и сохранять причину бана в нём, то никаких проблем с сохранением русского текста в samp.ban не будет.
    Значит причина, скорее всего, кроется в PVar'ах.
    Я решил проверить эту догадку, но только на SVar'ах, чтобы можно было провести тесты на пустом сервере. Всё равно механизм хранения в SVar'ах примерно тот же самый, разве что без привязки к игрокам.
    PHP код:
    #include <a_samp>

    main()
    {
        static 
    str[] = "AaBbZzАаБбЯя";
        new 
    str1[20], str2[20];
        
    str1[0] = '\0'strcat(str1strsizeof(str1));
        
    SetSVarString("reason"str);
        
    GetSVarString("reason"str2sizeof(str2));
        print(
    str1);
        print(
    str2);
        for (new 
    0;; ++i)
        {
            
    printf("[%d]\t%d\t%08x\t%c"istr1[i], str1[i], str1[i]);
            if (
    str1[i] == '\0')
                break;
        }
        for (new 
    0;; ++i)
        {
            
    printf("[%d]\t%d\t%08x\t%c"istr2[i], str2[i], str2[i]);
            if (
    str2[i] == '\0')
                break;
        }

    Здесь в один массив строка копируется прямиком из строковой константы, а в другой она копируется с промежуточным сохранением в SVar.
    После этого содержимое массивов выводится сначала полностью, а затем и посимвольно (с номерами позиций в строке и кодами символов).
    Вывод:
      Открыть/закрыть
    Код:
    AaBbZzАаБбЯя
    AaBbZzАаБбЯя
    [0]     65      00000041        A
    [1]     97      00000061        a
    [2]     66      00000042        B
    [3]     98      00000062        b
    [4]     90      0000005A        Z
    [5]     122     0000007A        z
    [6]     192     000000C0        А
    [7]     224     000000E0        а
    [8]     193     000000C1        Б
    [9]     225     000000E1        б
    [10]    223     000000DF        Я
    [11]    255     000000FF        я
    [12]    0       00000000
    [0]     65      00000041        A
    [1]     97      00000061        a
    [2]     66      00000042        B
    [3]     98      00000062        b
    [4]     90      0000005A        Z
    [5]     122     0000007A        z
    [6]     -64     FFFFFFC0        А
    [7]     -32     FFFFFFE0        а
    [8]     -63     FFFFFFC1        Б
    [9]     -31     FFFFFFE1        б
    [10]    -33     FFFFFFDF        Я
    [11]    -1      FFFFFFFF        я
    [12]    0       00000000

    Как и говорил Untonyst, второй массив после сохранения в SVar'е с помощью print и printf выводится нормально, но посмотрите внимательно на коды его последних символов:
    Код:
    [4]     90      0000005A        Z
    [5]     122     0000007A        z
    [6]     -64     FFFFFFC0        А
    [7]     -32     FFFFFFE0        а
    [8]     -63     FFFFFFC1        Б
    [9]     -31     FFFFFFE1        б
    [10]    -33     FFFFFFDF        Я
    [11]    -1      FFFFFFFF        я
    [12]    0       00000000
    Ага, у русских символов старшие байты ячеек установлены в FF (255) вместо 00 !
    Не удивительно, что они некорректно сохранялись в файл.

    Опытным путём удалось выяснить, что все символы с кодом от 0 до 127 обрабатываются корректно.
    Сам баг проявляется только в символах с кодами от 128 до 255 - среди них как раз и находятся символы кириллицы.
    Скорее всего, Kalcor символы из char в cell, из-за чего коды символов больше 127 после получения из PVar/SVar'а становились неправильными.

    Чтобы исправить символ, достаточно лишь установить старшие байты ячейки в 0, при этом оставив младший байт, как есть.
    Это можно легко сделать, выполнив побитовое И кода символа с числом 0x000000FF, т.к. будут действовать следующие правила:
    X & 0xFF = X
    X & 0x00 = 0
    В итоге получаем функцию:
    PHP код:
    FixSVarString(str[], size sizeof(str))
        for (new 
    0; ((str[i] &= 0xFF) != '\0') && (++!= size);) {} 
    (Ещё вместо побитового И можно было вычислить остаток от деления на 256, но для процессора эта операция куда более дорогостоящая.)

    Проверка. В отрывок кода, приведённый выше, ставим вызов получившейся функции:
    PHP код:
        SetPVarString(playerid"ban_reason""проверка");
        new 
    reason[128];
        
    GetPVarString(playerid"ban_reason"reasonsizeof(reason));
        
    DeletePVar(playerid"ban_reason");
        
    FixSVarString(reason);
        
    BanEx(playeridreason); 
    Вывод:
      Открыть/закрыть
    Код:
    AaBbZzАаБбЯя
    AaBbZzАаБбЯя
    [0]     65      00000041        A
    [1]     97      00000061        a
    [2]     66      00000042        B
    [3]     98      00000062        b
    [4]     90      0000005A        Z
    [5]     122     0000007A        z
    [6]     192     000000C0        А
    [7]     224     000000E0        а
    [8]     193     000000C1        Б
    [9]     225     000000E1        б
    [10]    223     000000DF        Я
    [11]    255     000000FF        я
    [12]    0       00000000
    [0]     65      00000041        A
    [1]     97      00000061        a
    [2]     66      00000042        B
    [3]     98      00000062        b
    [4]     90      0000005A        Z
    [5]     122     0000007A        z
    [6]     192     000000C0        А
    [7]     224     000000E0        а
    [8]     193     000000C1        Б
    [9]     225     000000E1        б
    [10]    223     000000DF        Я
    [11]    255     000000FF        я
    [12]    0       00000000

    Всё работает так, как и должно.

    Примерно то же самое я сделал в инклуде dc_kickfix.inc.
    Для проверки в тестовом моде снова ввёл команду /ban. В игре отличий никаких, но другое дело в samp.ban:
    Символы кириллицы записались так, как и должны были. Проблема решена.


    P.S.: Инклуд dc_kickfix.inc обновлён, баг с записью символов кириллицы исправлен.

    UPD: Инклуд fix_K2BEx.inc тоже дождался обновления.

    UPD[2]: Фикс принят в fixes.inc (вернее, в форк fixes.inc от ziggi - насколько я понял, он единственный, кто сейчас работает над обновлением инклуда):

    UPD (10.05.18): Как удалось выяснить, причина бага кроется во всё той же самопальной функции set_amxstring, уже успевшей отметиться в баге с выходом за пределы массива - видимо, создатели SA-MP решили использовать эту кривую самоделку, не осилив стандартную функцию amx_SetString.
    1. int set_amxstring(AMX* amx, cell amx_addr, const char* source, int max)
    2. {
    3. cell* dest = (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr));
    4. cell* start = dest;
    5. while (max--&&*source)
    6. *dest++=(cell)*source++;
    7. *dest = 0;
    8. return dest-start;
    9. }

    Обратите внимание на эту строку:
    1. *dest++=(cell)*source++;

    Как и предполагалось, при записи символа в массив Pawn происходит конверсия из char в cell, т.е. с расширением знакового бита. Значения больше 127 воспринимаются как отрицательные, например 128 - -128 (0x80), 129 - -127 (0x81), 130 - -126 (0x82), ..., 255 - -1 (0xFF), соответственно при конверсии в cell эти значения всё так же получаются отрицательными (0x80 => 0xFFFFFF80, 0x81 => 0xFFFFFF81, ..., 0xFF => 0xFFFFFFFF), со старшими байтами равными FF - отсюда и баг.
    Чтобы устранить эту проблему, достаточно указанную выше строку заменить на:
    1. *dest++=(cell)(unsigned char)*source++;

    После этого конверсия будет происходить из беззнакового типа (без расширения знакового бита) и баг будет устранён.


    Автор: Daniel_Cortez

    Специально для Pro-Pawn.ru
    Копирование данной статьи на других ресурсах без разрешения автора запрещено!
    Последний раз редактировалось Daniel_Cortez; 09.05.2018 в 23:42. Причина: информация о первопричине и способе исправления
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

  2. 16 пользователя(ей) сказали cпасибо:
    #Djuga (Вчера)#NickName (10.02.2016)$continue$ (16.12.2015)0x452 (10.05.2018)A N D R E Y (16.12.2015)Desulaid (16.12.2015)Kurbanoff (20.12.2015)L0ndl3m (16.12.2015)LLIapuk (17.12.2015)Nexius_Tailer (15.01.2016)Nurick (16.12.2015)Processing (03.06.2016)Profyan (17.12.2015)Prolific (30.03.2016)somebodies (07.06.2017)TheOrsini (20.01.2016)
  3. #2
    Аватар для Nexius_Tailer
    Пользователь

    Статус
    Оффлайн
    Регистрация
    04.01.2015
    Адрес
    Гомель, Беларусь
    Сообщений
    398
    Репутация:
    118 ±
    Полезная информация, спасибо за решение.
    Может, стоит написать об этом на официальный форум? (в раздел багов)

  4. #3
    Аватар для $continue$
    Заблокирован

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,467
    Репутация:
    265 ±
    Цитата Сообщение от Nexius_Tailer Посмотреть сообщение
    Полезная информация, спасибо за решение.
    Может, стоит написать об этом на официальный форум? (в раздел багов)
    Зачем мне это фиксить? Это же не ошибка безопасности. Да и есть возможность зафиксить скриптово (С) Kyeman

  5. #4
    Аватар для Nexius_Tailer
    Пользователь

    Статус
    Оффлайн
    Регистрация
    04.01.2015
    Адрес
    Гомель, Беларусь
    Сообщений
    398
    Репутация:
    118 ±
    Цитата Сообщение от $continue$ Посмотреть сообщение
    Зачем мне это фиксить? Это же не ошибка безопасности. Да и есть возможность зафиксить скриптово (С) Kyeman
    Отрепортить всё-же лучше, чем думать за него.

  6. #5
    Аватар для ziggi
    Проверенный

    Статус
    Оффлайн
    Регистрация
    14.05.2015
    Сообщений
    1,104
    Репутация:
    739 ±
    Цитата Сообщение от Nexius_Tailer Посмотреть сообщение
    Отрепортить всё-же лучше, чем думать за него.
    Не исправят, не надейся. Существуют целые библиотеки, которые исправляют баги сампа.
    По поводу функции: не понимаю, зачем так усложнять код?
    PHP код:
    stock FixAscii(text[])
    {
        for (new 
    0text[i] != '\0'i++) {
            
    text[i] &= 0xFF;
        }


  7. #6
    Аватар для Nexius_Tailer
    Пользователь

    Статус
    Оффлайн
    Регистрация
    04.01.2015
    Адрес
    Гомель, Беларусь
    Сообщений
    398
    Репутация:
    118 ±
    Цитата Сообщение от ziggi Посмотреть сообщение
    Не исправят, не надейся. Существуют целые библиотеки, которые исправляют баги сампа.
    По поводу функции: не понимаю, зачем так усложнять код?
    PHP код:
    stock FixAscii(text[])
    {
        for (new 
    0text[i] != '\0'i++) {
            
    text[i] &= 0xFF;
        }

    Если конкретно это уже есть в fixes.inc или ещё где, то и не надеялся бы)
    Думал, обнаружили недавно

    Хотя с другой стороны, мелкие баги медленно, но всё-же исправляются (просто об этом в релизе не упоминается)
    Последний раз редактировалось Nexius_Tailer; 16.01.2016 в 00:31.

  8. #7
    Аватар для ziggi
    Проверенный

    Статус
    Оффлайн
    Регистрация
    14.05.2015
    Сообщений
    1,104
    Репутация:
    739 ±
    Цитата Сообщение от Nexius_Tailer Посмотреть сообщение
    Если конкретно это уже есть в fixes.inc или ещё где, то и не надеялся бы)
    Думал, обнаружили недавно

    Хотя с другой стороны, мелкие баги медленно, но всё-же исправляются (просто об этом в релизе не упоминается)
    Добавил в свой форк fixes.inc (выключено по умолчанию): https://github.com/ziggi/sa-mp-fixes

  9. #8
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,767
    Репутация:
    2256 ±
    Цитата Сообщение от Nexius_Tailer Посмотреть сообщение
    Полезная информация, спасибо за решение.
    Может, стоит написать об этом на официальный форум? (в раздел багов)
    [sarcasm]Кому написать? Калькору, что ли?..[/sarcasm]


    Цитата Сообщение от ziggi Посмотреть сообщение
    Не исправят, не надейся. Существуют целые библиотеки, которые исправляют баги сампа.
    По поводу функции: не понимаю, зачем так усложнять код?
    PHP код:
    stock FixAscii(text[])
    {
        for (new 
    0text[i] != '\0'i++) {
            
    text[i] &= 0xFF;
        }

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

    ziggi, в твоём варианте "text[i] != '\0'" и "text[i] &= 0xFF" компилируются в два отдельных предложения, а компилятор оптимизирует код только в пределах одного предложения. Именно поэтому и приходится многие оптимизации делать вручную - ладно бы один раз происходило лишнее действие, но в цикле...
    Вообще из-за for получается пара лишних джампов, поэтому я бы порекомендовал вариант с циклом do-while:
    PHP код:
    static i= -1;
    do {} while((
    text[++i] &= 0xFF) != '\0'); 
    Код получится довольно простой, поэтому есть смысл подставить его прямиком в функции с фиксами для Get(P/S)VarString, а не выносить в отдельную функцию.

    P.S.: Описание бага в fixes.inc тоже неправильное. Багу подвержены не только символы кириллицы, а вообще все символы, код которых >= 128.
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

  10. #9
    Аватар для ziggi
    Проверенный

    Статус
    Оффлайн
    Регистрация
    14.05.2015
    Сообщений
    1,104
    Репутация:
    739 ±
    Цитата Сообщение от Daniel_Cortez Посмотреть сообщение
    ziggi, в твоём варианте "text[i] != '\0'" и "text[i] &= 0xFF" компилируются в два отдельных предложения, а компилятор оптимизирует код только в пределах одного предложения. Именно поэтому и приходится многие оптимизации делать вручную - ладно бы один раз происходило лишнее действие, но в цикле...
    Вообще из-за for получается пара лишних джампов, поэтому я бы порекомендовал вариант с циклом do-while:
    PHP код:
    do {} while((text[i] &= 0xFF) != '\0'); 
    Код получится довольно простой, поэтому есть смысл подставить его прямиком в функции с фиксами для Get(P/S)VarString, а не выносить в отдельную функцию.

    P.S.: Описание бага в fixes.inc тоже неправильное. Багу подвержены не только символы кириллицы, а вообще все символы, код которых >= 128.
    Спасибо, исправил.
    Просто я руководствуюсь простым правилом: если один код нужно выполнить больше, чем в одном месте, то его следует вынести в функцию. Но в данном случае согласен, скорость важнее.

  11. #10
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,767
    Репутация:
    2256 ±
    Цитата Сообщение от ziggi Посмотреть сообщение
    Спасибо, исправил.
    Просто я руководствуюсь простым правилом: если один код нужно выполнить больше, чем в одном месте, то его следует вынести в функцию. Но в данном случае согласен, скорость важнее.
    Если код достаточно простой, есть смысл инлайнить его. Многие компиляторы C/C++ обычно так и делают.
    Btw, я только что исправил свой пост выше, в примере не было инкремента переменной i и сама переменная не была объявлена (немного поспешил, у нас тут пары сейчас - я записываю конспекты на планшете быстрее, чем остальные в тетради, поэтому остаётся немного свободного времени). Замечу, здесь лучше использовать static, т.к. с new компилятору придётся вставить лишние опкоды для создания локальной переменной в стеке и высвобождения места. Лучше потратить 4 байта в секции данных, чем 16 в секции кода (4 на опкод push, 4 на операнд опкода, 4 на опкод stack и ещё 4 на операнд).
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

 

 
Страница 1 из 2 12 ПоследняяПоследняя

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •