Question

I seem to have hit the limits of my Pointer-Fu and am appealing for help (or some sort of brain medicine).

A rough outline of the project: An embedded ARM video-encoder board running Linux, using a manufacturer-supplied ill-documented poorly-supported SDK. Among its vast sprawl of code is a huge pile that is generated by gSoap from some WSDL, and it's this that is causing the headache.

In part of a huge data structure auto-generated by gSoap, we have a place to write some data (or, a place to write a pointer to the place we've written some data):

 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 */
 };

Then we have this code which, in short, should be writing a string to the 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);

The dns_string is what you'd expect - something like "192.168.2.254". It's correctly null-terminated, the value of LARGE_INFO_LENGTH is something big (like 1024) so plenty of room for the string. I changed from strcpy() to strncpy() for safety.

My background is smaller embedded stuff (no OS, no use of malloc()) so I'm having a bit of trouble convincing myself I understand what this code is doing. The code is auto-generated / part of the SDK so it's not my creation and it's not documented / commented.

Here's what I think it's doing:

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

Is allocating a lump of RAM, pointed to by DNSManual, where a tt__IPAddress struct will live.

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

Is allocating a lump of RAM, pointed to by IPv4Address, where a pointer to a string containing the address will be wrote.

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

Now this one throws me a bit, it looks like it's trying to allocate RAM to hold the string which will be pointed to by IPv4Address[0], except that looks to me like they're trying to write a (32-bit) pointer to a char, possibly.

This code has worked previously, however after some changes elsewhere it now segfaults, always at or during the strncpy().

My questions are twofold:

  1. Can someone help me properly understand what is going on with the mallocs / pointer-fu?
  2. Any guidance on how to go about tracing / debugging this?

We do not have GDB facility with this setup unfortunately - yes I'm sure it's possible to set it up, but for now let's just assume that's not practical for many lame and tedious reasons.

Currently I have debugging printf's scattered liberally through the code, in fact on every line in this little snippet, and it always stops with a SIGSEGV at the strncpy() line.


Edit to close as WhozCraig has hit the answer:

For reasons best known to itself, gSoap had changed the struct tt__IPAddress, perhaps it had run out of asterisks, but what it had been in previous versions, and what it should be, is this:

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

Solution

The code doesn't follow the structure layout. The layout is:

 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 */
 };

meaning: IPv4Address is a char pointer. yet this:

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

is assigning a char ** cast to it. but the type is still char * so this:

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

is dereferencing said pointer to a single char, which I can assure you is NOT compatible with a char * on your platform (and likely any other for that matter).

There should be warnings running amok on this compilation at a minimum, and outright errors if your compiler has any brains at all. This looks like it was original intended to be this:

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

for having a dynamic pointer array, each pointer being dynamically allocated memory for a single IP address. if it were like this, it would make much more sense. That said, if you intend on only a single IPv4 address per structure then this should be changed:

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;
    }
}

Or something similar to that.

OTHER TIPS

I think it looks broken.

This:

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

says IPv4Address is a single pointer to character data, i.e. a string.

But then it's used like this:

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

This is just wrong. Assuming a sane return value for soap_malloc() (i.e. void * to comply with malloc()), no cast should be necessary, but the fact that the cast differs from the actual type signal some kind of error.

It treats the IPv4Address struct field as a pointer to pointer, which it clearly is not.

I'm sure it should look similar to this:

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);

Your struct contains pointers to strings, but first it is allocating a array of pointers (char**) and then allocating memory for the first pointer in this array.

and don't forget to set the binary zero after you used strncpy() as it doesn't set it itself.

//Edit: First part was wrong, sorry

Here is a working solution (I used malloc instead of soap_malloc etc.):

#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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top