Путаница в реализации _TIFFVGetField при добавлении пользовательского тега - PullRequest
0 голосов
/ 29 февраля 2012

(Отказ от ответственности: я понимаю, что это огромная стена текста, но я приложил все усилия, чтобы свести все к основам. Если вы знакомы с libtiff, это не очень сложный вопрос)

Я уже задавал этот вопрос в списке рассылки libtiff, но я подумал, что у меня также может быть здесь хороший шанс, если есть кто-то, кто работал с библиотекой.

Я добавляю свой встроенныйтеги к библиотеке, используя документацию здесь: http://libtiff.maptools.org/addingtags.html

Итак, я добавил запись в массив TIFFFieldInfo, определенный в верхней части tif_dirinfo.c, примерно так:

{ TIFFTAG_CUSTOM_XXX, 4, 4, TIFF_SLONG, FIELD_XXX, 1, 0, "XXX" }, 

I тогдадобавил поле в структуру TIFFDirectory, определенную в tif_dir.h:

typedef struct {
    /* ... */
    int32 td_xxx[4]; 
} TIFFDirectory;

Теперь я пошел дальше и изменил _TIFFVSetField и _TIFFVGetField в соответствии с инструкциями.Это где я столкнулся с проблемой.

Подражая шаблону, уже присутствующему в библиотеке (см. Реализацию TIFFTAG_YCBCRSUBSAMPLING, что аналогично тому, что я делаю), я добавил следующий код в _TIFFVGetField:

/* existing, standard tag for reference */
case TIFFTAG_YCBCRSUBSAMPLING:
        *va_arg(ap, uint16*) = td->td_ycbcrsubsampling[0];
        *va_arg(ap, uint16*) = td->td_ycbcrsubsampling[1];
        break;
/* my new tag */
case TIFFTAG_CUSTOM_XXX: 
        *va_arg(ap, int32*) = td->td_xxx[0]; 
        *va_arg(ap, int32*) = td->td_xxx[1]; 
        *va_arg(ap, int32*) = td->td_xxx[2]; 
        *va_arg(ap, int32*) = td->td_xxx[3]; 
        break; 

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

_TIFFVGetField в конечном итоге вызывается из TIFFWriteNormalTag в tif_dirwrite.c.Соответствующий код такой:

case TIFF_LONG: 
    case TIFF_SLONG: 
    case TIFF_IFD: 
        if (fip->field_passcount) { 
            uint32* lp; 
            if (wc == (uint16) TIFF_VARIABLE2) { 
                TIFFGetField(tif, fip->field_tag, &wc2, &lp); 
                TDIRSetEntryCount(tif,dir, wc2); 
            } else {    /* Assume TIFF_VARIABLE */ 
                TIFFGetField(tif, fip->field_tag, &wc, &lp); 
                TDIRSetEntryCount(tif,dir, wc); 
            } 
            if (!TIFFWriteLongArray(tif, dir, lp)) 
                return 0; 
            } else { 
                if (wc == 1) { 
                    uint32 wp; 
                    /* XXX handle LONG->SHORT conversion */ 
                    TIFFGetField(tif, fip->field_tag, &wp); 
                    TDIRSetEntryOff(tif,dir, wp); 
                } else { 
                /* ---------------------------------------------------- */ 
                /* this is the code that is called in my scenario       */
                /* ---------------------------------------------------- */ 
                    uint32* lp; 
                    TIFFGetField(tif, fip->field_tag, &lp); 
                    if (!TIFFWriteLongArray(tif, dir, lp)) 
                        return 0; 
                } 
            } 
            break; 

Итак, неинициализированный указатель lp объявлен и его адрес передан TIFFGetField.Это, в свою очередь, устанавливает va_list (с lp в качестве единственного аргумента) и вызывает TIFFVGetField, который вызывает _TIFFVGetField с предоставленным va_list и указателем на неинициализированный указатель.

Здесь есть две проблемы.

Во-первых, вот как библиотека извлекает данные (мой код, но опять же, следуя уже существующему шаблону)

*va_arg(ap, int32*) = td->td_xxx[0]; 

Это кажется неверным.Это установка исходного указателя на значение типа int.Я предположил, что, возможно, в следующем примере (TIFFTAG_YCBCRSUBSAMPLING) эти целые числа были на самом деле адресами.Так хорошо, но даже если это так, хотя есть и другая проблема.

Библиотека вызывает va_args N раз, где N - количество элементов в массиве.Из того, что я вижу, список переменных-аргументов содержит только один аргумент (адрес указателя).Это неопределенное поведение в соответствии со стандартом (важный бит в начале):

Если фактического следующего аргумента нет или если тип не совместим с типом фактического следующего аргумента (в соответствии с продвижением аргументов по умолчанию), поведение не определено.

Правильная версия будет

*va_arg(ap, int32**) = td_xxx; 

Это устанавливает ранее неинициализированный указатель на массив, который действителен.Мне не нравится, что он указывает на сами данные, а не на копию, но что угодно;по крайней мере, это не дает сбой и дает мне правильный результат.

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

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

Любая помощь будет принята с благодарностью.Заранее спасибо.

1 Ответ

0 голосов
/ 25 марта 2012

Итак, ответ здесь в конечном итоге заключался в том, что libtiff полагается на UB для этого. Хотя технически это UB, я не смог найти реализацию va_arg, которая бы не делала что-то вроде этого:

( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

Таким образом, до тех пор, пока t меньше исходного размера аргумента (как здесь), вы можете безопасно вызывать va_arg более одного раза.

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

...