clinuxshellmemory-leaksvalgrind

Still reachable valgrind report when execve fails


I am building a shell as part of a school assignment, a limited version of bash. The whole program seems to function alright without any kind on leaks, but when I send invalid/nonexistent commands such as "lss", "Pwdd", etc. Or try to execute bash scripts with no permissions the still reachable reports in valgrind appears.

The following valgrind report snippet is from this execution:

(NOTE: The still reachable blocks that came from readline library are expect and do not need fixes, i attached one just for example.)

MINISHELL>$ lss

Command 'lss' not found.

MINISHELL>$ exit

(THIS STILL REACHABLE REPORT IS EXPECT, IS CAUSED BY READLINE LIBRARY)
==54202== 4 bytes in 1 blocks are still reachable in loss record 1 of 68
==54202==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==54202==    by 0x48AEBAC: xmalloc (in /usr/lib/x86_64-linux-gnu/libreadline.so.8.1)
==54202==    by 0x488C694: readline_internal_teardown (in /usr/lib/x86_64-linux-gnu/libreadline.so.8.1)
==54202==    by 0x4896D2A: readline (in /usr/lib/x86_64-linux-gnu/libreadline.so.8.1)
==54202==    by 0x109759: main (main.c:81)
==54202== 

(AT THIS POINT THE STILL REACHABLES COME FROM MY CODE, THIS IS THE ISSUE)
==54202== 4 bytes in 1 blocks are still reachable in loss record 2 of 68
==54202==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==54202==    by 0x109F0E: add_token (tokenizer.c:168)
==54202==    by 0x10A458: split_tokens (tokenizer.c:291)
==54202==    by 0x10A5A0: lexing_input (tokenizer.c:321)
==54202==    by 0x109809: main (main.c:101)
==54202== 
==54202== 4 bytes in 1 blocks are still reachable in loss record 3 of 68
==54202==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==54202==    by 0x10FACD: ft_strdup (in /home/rache/programing/42/Common Core/minishell/MINISHELL_DUO_REPO/minishell)
==54202==    by 0x10C48E: copy_simple_cmd (collect_commands.c:47)
==54202==    by 0x10C5E6: collect_commands (collect_commands.c:75)
==54202==    by 0x10C79B: format_parsed_data (collect_commands.c:118)
==54202==    by 0x109829: main (main.c:105)
{...}
==54199== LEAK SUMMARY:
==54199==    definitely lost: 0 bytes in 0 blocks
==54199==    indirectly lost: 0 bytes in 0 blocks
==54199==      possibly lost: 0 bytes in 0 blocks
==54199==    still reachable: 208,214 bytes in 225 blocks
==54199==         suppressed: 0 bytes in 0 blocks

I think i cant come with a minimal reproducible code, so i will do the full reproducible code:

Clone the repository:

git clone https://github.com/rach3bartmoss/minishell.git

Run:

make && make clean

execute with valgrind, will also create a file containing the report:

valgrind --leak-check=full  --show-leak-kinds=all --track-origins=yes --log-file=01valgrind_report.txt ./minishell
then run just the 'lss' or any command with a typo, and run 'exit'

you can try any basics stuff, such cd's commands, commands with pipes, redirections (<, <<, >> and >), can use export VAR=something, unset VAR, env, echo $VAR, and other but i think it got a point. Again, if you run commands that don't causes any errors then will not have leaks, do these steps above and run "lss", then immediately exit the program and check the valgrind report, you will see the backtrace to the functions, the issue is that in the past 5 days I mingled and search and try to re-write some functions by myself, retries with chatgpt, nothing takes this still reachable codes away.

My basic logic path of the program is:

  1. user input reading with readline()

  2. pass this input to a lexer to divide the input into tokens, generate a t_lexer *lexer struct

  3. takes the tokens created by lexer and fill with meaningful information to run, parsing phase, it sets the input and output fds, sets the name of command and check if exists in the binaries, if has arguments organize, if the command come with a environment variable it expands to its value, among other things.

  4. the parsed structure is ready, goes to the exec_parsed_cmds to be executed by a execve call in the child process, if is a built in commands, is run without fork, not in a child process.

I think the issue is that when a execve call fails, it also fails to free the lexe'd/ parsed input, is a guess, because i just dont see how, i have solid functions that frees it, in the main.c we got the main loop that end of each run it frees all the lexer and parsed data, thats why in the normal behavior we dont have leaks, the leaks happens only when the execve fails.

I know is a big text, I appreciate anyone who can help, i tried here to be as clear as possible but any question i am available.

(NOTE: You my think why i use ft_strdup instead of strdup or other standard functions, i am limited by school to use a certain set of functions for this project, any other function i must code myself, besides that i dont think any of these replicas of are the issue, they are simple and straightforward.)


Solution

  • The problem is likely because you're not freeing up all memory allocated after a call to exec fails.

    If you're only ever calling fork to then call exec, then you're probably not actually interested in leaks in the child process. That being the case, you should add the --child-silent-after-fork=yes option to prevent tracing child processes.