2

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

4
  • Some may argue that still reachable are not reason to worry about, and do not means a leak since the pointers are not lost and the OS will cleanup after the process ends, anyhow i need to understand the cause, since it can be indeed a malloc with no free. Commented Aug 16, 2025 at 19:13
  • You oneed to post a minimal reproducible example that demonstrates the problem. It sounds like you're not properly exiting in the child process when execve() fails. Commented Aug 16, 2025 at 19:34
  • And whether or not the execve succeeds, the parent process needs to free any memory it allocated to process the command. Commented Aug 16, 2025 at 19:35
  • @Barmar and indeed i do free any memory allocated in the parent. at the very bottom of main.c i got theses calls: cleanup_iter(lexer, &pd); -> frees the lexer and parsed data free (input); ->frees the input string readed from readline. Commented Aug 16, 2025 at 20:04

1 Answer 1

4

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.

answered Aug 16, 2025 at 20:25
Sign up to request clarification or add additional context in comments.

7 Comments

I tested, and worked, the valgrind did not report any other still reachable leak besides from readline library, this proves that the leak is indeed in the child, i can assume that the kernel cleans the entire memory in the child when it terminates?
Yes. On almost all modern systems, and on anything supporting POSIX, you can assume that all the memory allocated for a process is recovered by the system when the process terminates.
... which means, @RacheBartmoss, that at least for such systems, you should apply some thought to what Valgrind tells you. You won't likely see it in your shell project, but in programs that allocate lots of small bits of memory, it can make termination noticeably slow, for no good reason, to painstakingly seek and free every single allocated object. The main advantage is in debugging, to reduce noise to help you recognize the leaks that are actually consequential.
@RacheBartmoss But it's still generally considered best to clean up memory as soon as it's not needed. Otherwise, if the process runs for a long time (like an interactive shell often does) it may accumulate lots of garbage.
even if the leaks are in a child process created just to run a command? In my case, the still reachable reports happens after the fork, but once the in the child process, if the execve failed is when the still reachable happens, next thing i do is to cleanup the memory i just used and exit the process, a few residual bytes somehow does not get freed, since the child process is ended, the kernel will cleanup the child process memory immediately? (edit) just read in documentation, that the kernel will clean after the parente wait/waitpid call.
Whenever a process exits, the kernel reclaims all of that process's memory. Whether that process called exec or not doesn't matter.
@dbush But valgrind reports memory that wasn't freed as a leak, even though it will be reclaimed. The whole point of this is to help you keep the memory use as minimal as possible while the process is running. Leaving all the cleanup to the OS when you exit is not "clean".

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.