Почему этот указатель-фу segfault?

Кажется, я достиг предела своего Пойнтер-Фу и обращаюсь за помощью (или к какому-то лекарству для мозга).

Приблизительный план проекта: встроенная плата видеокодировщика ARM под управлением Linux с использованием поставляемого производителем плохо документированного и плохо поддерживаемого SDK. Среди его обширного разбросанного кода есть огромная куча, созданная gSoap из какого-то WSDL, и именно это вызывает головную боль.

В части огромной структуры данных, автоматически сгенерированной gSoap, у нас есть место для записи некоторых данных (или место для записи указателя на место, где мы записали некоторые данные):

 struct tt__IPAddress
 {
    enum tt__IPType Type;   /* required element of type tt:IPType */
    char *IPv4Address;  /* optional element of type tt:IPv4Address */
    char *IPv6Address;  /* optional element of type tt:IPv6Address */
 };

Затем у нас есть этот код, который, короче говоря, должен записывать строку в IPv4Address:

DNSInformation->DNSManual = ((struct tt__IPAddress *)soap_malloc(soap, sizeof(struct tt__IPAddress)));
DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap, sizeof(char *));
DNSInformation->DNSManual->IPv4Address[0] = (char *)soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);
// Code crashes at this next line:
strncpy(*DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);

Строка dns — это то, что вы ожидаете — что-то вроде «192.168.2.254». Он правильно завершается нулем, значение LARGE_INFO_LENGTH является чем-то большим (например, 1024), поэтому для строки достаточно места. Я перешел с strcpy() на strncpy() для безопасности.

В моем опыте встроенные программы меньшего размера (без ОС, без использования malloc()), поэтому мне трудно убедить себя, что я понимаю, что делает этот код. Код генерируется автоматически / является частью SDK, поэтому это не мое творение, и он не документирован / не прокомментирован.

Вот что я думаю, что это делает:

DNSInformation->DNSManual = ((struct tt__IPAddress *)soap_malloc(soap, sizeof(struct tt__IPAddress)));

Выделяет кусок ОЗУ, на который указывает DNSManual, где будет жить структура tt__IPAddress.

DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap, sizeof(char *));

Выделяет кусок оперативной памяти, на который указывает IPv4Address, куда будет записан указатель на строку, содержащую адрес.

DNSInformation->DNSManual->IPv4Address[0] = (char *)soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);

Теперь это меня немного смущает, похоже, что он пытается выделить ОЗУ для хранения строки, на которую будет указывать IPv4Address[0], за исключением того, что мне кажется, что они пытаются написать (32-битный) указатель на обуглку, возможно.

Этот код работал раньше, однако после некоторых изменений в других местах он теперь дает сбои, всегда во время или во время выполнения strncpy().

У меня два вопроса:

  1. Может ли кто-нибудь помочь мне правильно понять, что происходит с mallocs/pointer-fu?
  2. Любое руководство о том, как это отследить/отладить?

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

В настоящее время у меня есть отладочные printf, разбросанные по коду, фактически в каждой строке этого маленького фрагмента, и он всегда останавливается с SIGSEGV в строке strncpy().


Изменить, чтобы закрыть, поскольку WhozCraig нажал ответ:

По причинам, наиболее известным самому себе, gSoap изменил структуру tt__IPAddress, возможно, в ней закончились звездочки, но то, что было в предыдущих версиях и каким оно должно было быть, это:

struct tt__IPAddress
 {
    enum tt__IPType Type; 
    char **IPv4Address;  /* note ptr to ptr */
    char **IPv6Address;
 };

person John U    schedule 27.08.2013    source источник
comment
Первая, сразу очевидная ошибка заключается в том, что вы приводите возвращаемое значение из malloc().   -  person    schedule 27.08.2013
comment
@H2CO3 Добро пожаловать в мыльницу, спасибо за ссылку. :)   -  person unwind    schedule 27.08.2013
comment
@unwind, а ты никогда не видел, чтобы я ссылался на твой ответ? :) Я занимаюсь этим довольно давно. (К сожалению, это часто необходимо. Слишком часто.)   -  person    schedule 27.08.2013
comment
Код не совсем согласован, не так ли? Ваш компилятор обязательно должен предупреждать вас о неявном преобразовании (char**) в (char*). Конечно, объявление структуры неверно!   -  person Nicholas Wilson    schedule 27.08.2013
comment
@H2CO3. В свою защиту: я ничего не привожу, это делает ужасный сгенерированный код, который я вынужден модифицировать. К сожалению, мне нужна небольшая помощь по части 1 моего вопроса, чтобы полностью понять, что происходит.   -  person John U    schedule 27.08.2013
comment
@JohnU Упс, тогда извини, что обвинил тебя в этом. (В этом случае сгенерированный код действительно ужасен.) Что касается объяснения, Николас Уилсон сделал несколько справедливых замечаний. (Кроме того, мне понравилось слово «указатель-фу»: P)   -  person    schedule 27.08.2013
comment
Без дополнительного контекста трудно сказать, что неправильно, определение структуры или опубликованный код. Либо IPvAddress должен быть char**, либо более поздний код не должен выполнять выделение sizeof(char) и должен рассматривать поле как указатель на строку, а не указатель на таблицу указателей.   -  person Nicholas Wilson    schedule 27.08.2013
comment
@ H2CO3 Я уверен, что да, я не хотел, чтобы это звучало так, как будто я игнорировал ваши предыдущие усилия. И я конечно согласен, страшно как часто выкладывают код с приведениями. :|   -  person unwind    schedule 27.08.2013
comment
Не могли бы вы показать нам DNSInformation и DNSManual конструкции?   -  person Uchia Itachi    schedule 27.08.2013
comment
@unwind А нет, я просто был удивлен :) Я тоже часто не могу не качать головой, когда кто-то снова сделал чертовски неправильное заклинание...   -  person    schedule 27.08.2013


Ответы (4)


Код не соответствует макету структуры. Макет:

 struct tt__IPAddress
 {
    enum tt__IPType Type;   /* required element of type tt:IPType */
    char *IPv4Address;  /* optional element of type tt:IPv4Address */
    char *IPv6Address;  /* optional element of type tt:IPv6Address */
 };

значение: IPv4Address является указателем char. еще это:

DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap, sizeof(char *));

присваивает ему char ** приведение. но тип по-прежнему char *, так что:

strncpy(*DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);

разыменовывает указанный указатель на один char, который, я могу вас заверить, НЕ совместим с char * на вашей платформе (и, вероятно, любой другой в этом отношении).

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

 struct tt__IPAddress
 {
    enum tt__IPType Type; 
    char **IPv4Address;  /* note ptr to ptr */
    char **IPv6Address;
 };

для наличия динамического массива указателей, каждый указатель представляет собой динамически выделяемую память для одного IP-адреса. если бы это было так, это имело бы гораздо больше смысла. Тем не менее, если вы планируете использовать только один IPv4-адрес для каждой структуры, это следует изменить:

DNSInformation->DNSManual = soap_malloc(soap, sizeof(struct tt__IPAddress)));
if (DNSInformation->DNSManual)
{
    DNSInformation->DNSManual->IPv4Address = soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);
    if (DNSInformation->DNSManual->IPv4Address)
    {
        strncpy(DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);
        DNSInformation->DNSManual->IPv4Address[LARGE_INFO_LENGTH-1] = 0;
    }
}

Или что-то похожее на это.

person WhozCraig    schedule 27.08.2013
comment
Вы попали в самую точку, сэр — проверка предыдущей версии автоматически сгенерированного кода показала, что действительно было две звездочки. Предупреждения компилятора были, но даже при работе (в комплекте) SDK генерирует около 10 000 предупреждений, так что они немного затерялись в шуме. Можно только догадываться, почему gSoap вдруг решил снять звезду, но теперь я знаю, что нужно внимательно следить за этим. Большое спасибо! - person John U; 28.08.2013
comment
@JohnU 10 000 предупреждений? Ой. Я бы серьезно отнесся к поиску дополнения или полной замены. Как видите, обычно они означают что-то вроде «Я не уверен, что это хорошая идея»; ты действительно хотел это сделать?. В любом случае, я рад, что это помогло. - person WhozCraig; 28.08.2013
comment
Он большой, и, к сожалению, это единственное, что включает в себя функции, которые требуются нашим клиентам для доступного оборудования, без лишних нуля или трех в конце бюджета, чтобы нанять команду программистов. Переключение подробного режима компилятора выводит один миллион строк текста — я начал писать синтаксические анализаторы только для того, чтобы что-то уловить! - person John U; 29.08.2013

Я думаю, что это выглядит сломанным.

Этот:

char *IPv4Address;  /* optional element of type tt:IPv4Address */

говорит, что IPv4Address является единственным указателем на символьные данные, то есть на строку.

Но затем он используется следующим образом:

DNSInformation->DNSManual->IPv4Address = (char **)soap_malloc(soap,
                                                              sizeof(char *));

Это неправильно. Предполагая разумное возвращаемое значение для soap_malloc() (т.е. void * для соответствия malloc()), приведение не требуется, но тот факт, что приведение отличается от фактического типа, сигнализирует о какой-то ошибке.

Он обрабатывает поле структуры IPv4Address как указатель на указатель, что явно не так.

person unwind    schedule 27.08.2013

Я уверен, что это должно выглядеть примерно так:

DNSInformation->DNSManual = soap_malloc(soap, sizeof(struct tt__IPAddress)));
DNSInformation->DNSManual->IPv4Address = soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);

strncpy(DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH-1);

Ваша структура содержит указатели на строки, но сначала она выделяет массив указателей (char**), а затем выделяет память для первого указателя в этом массиве.

и не забудьте установить двоичный ноль после того, как вы использовали strncpy(), так как он не устанавливает его сам.

//Редактировать: Первая часть была неправильной, извините

person robin.koch    schedule 27.08.2013
comment
*x и x[0] одинаковы. - person Shahbaz; 27.08.2013
comment
вы правы, спасибо за подсказку. Я действительно не думал об этом. - person robin.koch; 27.08.2013
comment
Истинность утверждения *x и x[0]одно и то же зависит от того, на каком этапе жизни находится x. true только после создания x. При создании объявление x в форме int x[0]; не работает. Но объявление int *a; делает. - person ryyker; 13.09.2013

Вот рабочее решение (я использовал malloc вместо soap_malloc и т. д.):

#include <stdio.h>
#include <stdlib.h>

#define LARGE_INFO_LENGTH 1024

enum tt__IPType { tt__IPv4, tt__IPv6 };

struct tt__IPAddress
{
  enum tt__IPType Type;   /* required element of type tt:IPType */
  char *IPv4Address;  /* optional element of type tt:IPv4Address */
  char *IPv6Address;  /* optional element of type tt:IPv6Address */
};

struct tt__DNSInformation
{
  struct tt__IPAddress* DNSManual;
};

int main()
{
  struct tt__DNSInformation* DNSInformation;
  char dns_string[] = "192.168.2.254";

  DNSInformation = malloc(sizeof(struct tt__DNSInformation));
  DNSInformation->DNSManual = malloc(sizeof(struct tt__IPAddress));
  DNSInformation->DNSManual->IPv4Address = malloc(sizeof(char) * LARGE_INFO_LENGTH);
  strncpy(DNSInformation->DNSManual->IPv4Address, dns_string, LARGE_INFO_LENGTH - 1);

  printf("%s\n", DNSInformation->DNSManual->IPv4Address);
  return 0;
}
person kol    schedule 27.08.2013
comment
Примечание: type *pointer = malloc(size * sizeof(*pointer)) короче, менее подвержено ошибкам, более ориентировано на будущее, проще в обслуживании и не повторяется по сравнению с type *pointer = (type *)malloc(size * sizeof(type)). - person Shahbaz; 27.08.2013
comment
@Shahbaz Спасибо, я удалил слепки. - person kol; 27.08.2013