I'm hacking teeworlds' server code, however I'm experimenting a strange bug. I've already encountered something very similar, but I've found a not clean work around.
As I said in the post title, I've a segfault that appears only in release build. I located the line using printfs, because neither valgrind nor gdb show errors with the debug build.
this is the full code, the segfault occurs after have called this function :
void CServer::SetClientName(int ClientID, const char *pName)
{
if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY)
return;
if(!pName)
return;
char * pDispName;
char aCleanName[MAX_NAME_LENGTH];
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST");
// clean name
int lastpos = 0;
{
int i = 0;
const char *psrc = pName;
char *pdst = aCleanName;
for(; *psrc != '\0' && *psrc<=' '; ++psrc);
while(*psrc != '\0' && i<sizeof(aCleanName)-1)
{
if(*psrc <= ' ')
{
*pdst = ' ';
}
else
{
*pdst = *psrc;
lastpos = i+1;
}
++psrc;
++pdst;
++i;
}
aCleanName[lastpos] = '\0'; //Rtrim
}
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST2");
/////////////// These lines if not commented prevent the segfault
// char aBuf[256];
// str_format(aBuf, sizeof(aBuf), "CleanName : '%s, lastpos = %i'", aCleanName, lastpos);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", aCleanName);
// set real name
str_copy(m_aClients[ClientID].m_aName, aCleanName, sizeof(aCleanName));
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST3");
// DispName
if(m_aClients[ClientID].m_aUserAcc[0] != '\0')
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "AUTHED");
pDispName = m_aClients[ClientID].m_aName;
}
else
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "NOT AUTHED");
str_percent_format(aCleanName, sizeof(aCleanName), g_Config.m_SvNotAuthedFormat, m_aClients[ClientID].m_aName);
pDispName = aCleanName;
}
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST4");
if(TrySetClientDispName(ClientID, pDispName))
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TRY FAILED");
// auto rename
int j = 0;
for( ; j<MAX_NAME_LENGTH-3 && aCleanName[j]!='\0' ; ++j);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST5");
for( ; j<MAX_NAME_LENGTH-3; ++j)
aCleanName[j] = ' ';
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST6");
aCleanName[MAX_NAME_LENGTH-3] = '#';
aCleanName[MAX_NAME_LENGTH-2] = 'a';
aCleanName[MAX_NAME_LENGTH-1] = '\0';
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST7");
for(int i = 0; i<MAX_CLIENTS; ++i) //avoid infinite loop
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST9");
if(TrySetClientDispName(ClientID, aCleanName) == 0)
break;
++aCleanName[MAX_NAME_LENGTH-2];
}
}
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST10");
}
TEST10 is printed, but the statement fallowing the call is never
Note : TrySetClientDispName(ClientID, aCleanName)
doesn't store pointers to aCleanName,
str_format
is implementing as following :
void str_format(char *buffer, int buffer_size, const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsnprintf(buffer, buffer_size, format, ap);
va_end(ap);
buffer[buffer_size-1] = 0; /* assure null termination */
}
I guess, it's a stack corruption, but where ?
And then why does the str_format
prevent the bug ?
EDIT :
Maybe str_percent_format(aCleanName, sizeof(aCleanName), g_Config.m_SvNotAuthedFormat, m_aClients[ClientID].m_aName)
write more in aCleanName than it can hold