NetICMP_TxEchoReq() Returns Wrong Error Code
Closed this issue · 3 comments
System Configuration
The following is my configuration.
- uc/USB: CDC-ACM
- uc/USB: CDC-EEM
- uc/Shell: Shell
- uc/Shell: Terminal
- uc/TCPIP
The terminal is accessed through a serial port which is hosted over the CDC-ACM port. The shell at startup registered all net commands.
Bug Overview
NetICMP_TxEchoReq() function can return the wrong error code. In the following code, if kal_err != KAL_ERR_NONE
then an error has occurred. For any error after setting p_err
the software will issue a jump to release
.
switch (kal_err) {
case KAL_ERR_NONE:
result = DEF_OK;
*p_err = NET_ICMP_ERR_NONE;
goto release;
case KAL_ERR_TIMEOUT:
result = DEF_FAIL;
*p_err = NET_ICMP_ERR_ECHO_REQ_TIMEOUT;
goto release;
case KAL_ERR_ABORT:
case KAL_ERR_ISR:
case KAL_ERR_WOULD_BLOCK:
case KAL_ERR_OS:
default:
result = DEF_FAIL;
*p_err = NET_ICMP_ERR_ECHO_REQ_SIGNAL_FAULT;
goto release;
}
release:
NetICMP_LockAcquire(p_err);
KAL_SemDel(sem_handle, &kal_err);
if (*p_err == NET_ICMP_ERR_NONE) {
if (data_len > 0) {
if (p_echo_req_handle_new->DataCmp != DEF_OK) {
*p_err = NET_ICMP_ERR_ECHO_REPLY_DATA_CMP_FAIL;
}
}
}
Once execution resumes at release
, the first function, NetICMP_LockAcquire(p_err)
, will overwrite p_err
resulting in any previous error code being lost.
Reproduction
- Start system. System not connected to internet.
- Connect to system over serial port (using USB CDC-ACM).
- Issue a net_ping command using IP address that is not on local network.
- Shell prints error error code 12033 (
NET_ICMP_ERR_ECHO_REPLY_DATA_CMP_FAIL
).
Shell should return NET_ICMP_ERR_ECHO_REQ_TIMEOUT
.
Debugging shows that p_err
does get set correctly to NET_ICMP_ERR_ECHO_REQ_TIMEOUT
, but is overwritten once code following release
starts executing.
Resolution
If the switch statement is changes so that errors use goto exit_kal_fault
instead of goto release
, the system will not call Mem_DynPoolBlkFree()
which results in a memory leak. Since this code uses goto, the following code section
exit_kal_fault:
Mem_DynPoolBlkFree(&NetICMP_DataPtr->EchoReqPool,
p_echo_req_handle_new,
&lib_err);
exit_release_lock:
NetICMP_LockRelease();
exit_return:
return (result);
could be replaced with
exit_kal_fault:
Mem_DynPoolBlkFree(&NetICMP_DataPtr->EchoReqPool,
p_echo_req_handle_new,
&lib_err);
exit_release_lock:
NetICMP_LockRelease();
goto exit_return;
exit_kal_fault_with_cleanup:
Mem_DynPoolBlkFree(&NetICMP_DataPtr->EchoReqPool,
p_echo_req_handle_new,
&lib_err);
exit_return:
return (result);
where exit_kal_fault_with_cleanup
will be used in the switch case instead of goto release
for the fault conditions.
As I can't find a coding standard I do not know what the appropriate solution would be.
Hi SeanAlling,
Thank you for bringing this up. I think a better solution would be to create a separate local variable of type NET_ERR that is passed to the NetICMP_LockAcquire() function calls and use p_err separately.
In the case where KAL_SemPend()
has failed, should ``release:even be executed? My assumption was that in the case where
KAL_SemPend()```, there is a known error so the code should immediately release the allocated memory and return.
If I understand your intent, then even when KAL_SemPend()
fails, then the NetICMP_LockAcquire()
should still be run.
An alternative to what I proposed could be the following
if(*p_err == KAL_ERR_NONE) {
NetICMP_LockAcquire(p_err);
KAL_SemDel(sem_handle, &kal_err);
if (*p_err == NET_ICMP_ERR_NONE) {
if (data_len > 0) {
if (p_echo_req_handle_new->DataCmp != DEF_OK) {
*p_err = NET_ICMP_ERR_ECHO_REPLY_DATA_CMP_FAIL;
}
}
}
if ((NetICMP_DataPtr->EchoReqHandleListStartPtr == p_echo_req_handle_new) &&
(NetICMP_DataPtr->EchoReqHandleListEndPtr == p_echo_req_handle_new)) {
NetICMP_DataPtr->EchoReqHandleListStartPtr = DEF_NULL;
NetICMP_DataPtr->EchoReqHandleListEndPtr = DEF_NULL;
} else if (NetICMP_DataPtr->EchoReqHandleListStartPtr == p_echo_req_handle_new) {
NetICMP_DataPtr->EchoReqHandleListStartPtr = p_echo_req_handle_new->NextPtr;
} else if (NetICMP_DataPtr->EchoReqHandleListEndPtr == p_echo_req_handle_new) {
NetICMP_DataPtr->EchoReqHandleListEndPtr = p_echo_req_handle_new->PrevPtr;
} else {
p_echo_req_handle_end = p_echo_req_handle_new->PrevPtr;
p_echo_req_handle_end->NextPtr = p_echo_req_handle_new->NextPtr;
}
p_echo_req_handle_new->NextPtr = DEF_NULL;
p_echo_req_handle_new->PrevPtr = DEF_NULL;
}
exit_kal_fault:
Mem_DynPoolBlkFree(&NetICMP_DataPtr->EchoReqPool,
p_echo_req_handle_new,
&lib_err);
exit_release_lock:
if(*p_err == KAL_ERR_NONE) {
NetICMP_LockRelease();
}
The above would make the entire release branch and the exit_release_lock branch of code not execute when KAL_SemPend()
returned with an error.
The if around NetICMP_LockRelease()
but I like to keep the number of lock and release calls equal, and with out the f condition on the exit_release_lock
then an additional LockRelease would execute.
This alternative proposed method also wont need an additional NET_ERR variable created and also does not need any extra code written for making sure the correct error code is set in p_err.
After some more testing of this today, I realized my last remark had a memory leak. KAL_SemDel() would not be called when an error occurred which would prevent the created semaphore from being deleted.
The fix is a small change to the if statement.
release:
if(p_err == NET_ICMP_ERR_NONE) {
NetICMP_LockAcquire(p_err);
KAL_SemDel(sem_handle, &kal_err);
if (*p_err == NET_ICMP_ERR_NONE) {
if (data_len > 0) {
if (p_echo_req_handle_new->DataCmp != DEF_OK) {
*p_err = NET_ICMP_ERR_ECHO_REPLY_DATA_CMP_FAIL;
}
}
}
if ((NetICMP_DataPtr->EchoReqHandleListStartPtr == p_echo_req_handle_new) &&
(NetICMP_DataPtr->EchoReqHandleListEndPtr == p_echo_req_handle_new)) {
NetICMP_DataPtr->EchoReqHandleListStartPtr = DEF_NULL;
NetICMP_DataPtr->EchoReqHandleListEndPtr = DEF_NULL;
} else if (NetICMP_DataPtr->EchoReqHandleListStartPtr == p_echo_req_handle_new) {
NetICMP_DataPtr->EchoReqHandleListStartPtr = p_echo_req_handle_new->NextPtr;
} else if (NetICMP_DataPtr->EchoReqHandleListEndPtr == p_echo_req_handle_new) {
NetICMP_DataPtr->EchoReqHandleListEndPtr = p_echo_req_handle_new->PrevPtr;
} else {
p_echo_req_handle_end = p_echo_req_handle_new->PrevPtr;
p_echo_req_handle_end->NextPtr = p_echo_req_handle_new->NextPtr;
}
p_echo_req_handle_new->NextPtr = DEF_NULL;
p_echo_req_handle_new->PrevPtr = DEF_NULL;
}
else {
KAL_SemDel(sem_handle, &kal_err);
}
Same as my prior recommendation, the block of code between the release
tag and the exit_kal_fault
will now not execute if a fault is detected. The code will then execute the else block, deleting the semaphore, and then continue at exit_kal_fault
tag. If no error is detected, the code will run the same as before.