It turns out that on my actual target, declaring to_free as volatile does
NOT solve the leak problem.
Here is the diff of the hush.c source between my previous solution (putting
to_free in the globals, which does solve the leak) and the volatile attempt
at the same (I left it declared in the definition of the global struct in
both cases):
--- Vhush.c 2025-08-07 13:13:42.863447663 -0400
+++ Ghush.c 2025-08-07 13:53:53.171455825 -0400
@@ -7731,8 +7732,8 @@
pid_t pid;
int channel[2];
# if !BB_MMU
- volatile char ** to_free = NULL;
-// G.to_free = NULL;
+// volatile char ** to_free = NULL;
+ G.to_free = NULL;
# endif
xpipe(channel);
@@ -7810,7 +7810,7 @@
* huge=`cat BIG` # was blocking here forever
* echo OK
*/
- re_execute_shell(&to_free,
+ re_execute_shell(&G.to_free,
s,
G.global_argv[0],
G.global_argv + 1,
@@ -7828,9 +7828,9 @@
# endif
enable_restore_tty_pgrp_on_exit();
# if !BB_MMU
- free(to_free);
-// free(G.to_free);
-// G.to_free = NULL;
+// free(to_free);
+ free(G.to_free);
+ G.to_free = NULL;
# endif
close(channel[1]);
return channel[0];
And here is the diff from running "objdump -d -j
.text.generate_stream_from_string hush.o" from the two different builds.
Note that I removed the first 5 character from each line (the address
offset from the start of the function) because otherwise the different
address offsets pollutes the diff too much to be useful:
--- Vhush.s 2025-08-07 13:54:11.735455888 -0400
+++ Ghush.s 2025-08-07 13:54:11.735455888 -0400
@@ -6,79 +6,84 @@
000 <generate_stream_from_string>:
b530 push {r4, r5, lr}
- b08b sub sp, #44 ; 0x2c
+ b089 sub sp, #36 ; 0x24
9004 str r0, [sp, #16]
- 2300 movs r3, #0
- a808 add r0, sp, #32
+ a806 add r0, sp, #24
f8cd 900c str.w r9, [sp, #12]
9105 str r1, [sp, #20]
- 9307 str r3, [sp, #28]
f7ff fffe bl 0 <xpipe>
f7ff fffe bl 0 <vfork>
2800 cmp r0, #0
f8dd 900c ldr.w r9, [sp, #12]
- da03 bge.n 2a <generate_stream_from_string+0x2a>
- 4827 ldr r0, [pc, #156] ; (c0
<generate_stream_from_string+0xc0>)
+ da03 bge.n 26 <generate_stream_from_string+0x26>
+ 482d ldr r0, [pc, #180] ; (d4
<generate_stream_from_string+0xd4>)
4478 add r0, pc
f7ff fffe bl 0 <bb_simple_perror_msg_and_die>
- d138 bne.n 9e <generate_stream_from_string+0x9e>
- 4b25 ldr r3, [pc, #148] ; (c4
<generate_stream_from_string+0xc4>)
+ d138 bne.n 9a <generate_stream_from_string+0x9a>
+ 4b2b ldr r3, [pc, #172] ; (d8
<generate_stream_from_string+0xd8>)
2101 movs r1, #1
f859 2003 ldr.w r2, [r9, r3]
- 4b24 ldr r3, [pc, #144] ; (c8
<generate_stream_from_string+0xc8>)
+ 4b2a ldr r3, [pc, #168] ; (dc
<generate_stream_from_string+0xdc>)
f44f 00e0 mov.w r0, #7340032 ; 0x700000
444b add r3, r9
6013 str r3, [r2, #0]
f7ff fffe bl 0 <bb_signals>
- 9808 ldr r0, [sp, #32]
+ 9806 ldr r0, [sp, #24]
f7ff fffe bl 0 <close>
2101 movs r1, #1
- 9809 ldr r0, [sp, #36] ; 0x24
+ 9807 ldr r0, [sp, #28]
f8dd 900c ldr.w r9, [sp, #12]
f7ff fffe bl 0 <xmove_fd>
9804 ldr r0, [sp, #16]
f7ff fffe bl 0 <skip_whitespace>
- 491c ldr r1, [pc, #112] ; (cc
<generate_stream_from_string+0xcc>)
+ 4922 ldr r1, [pc, #136] ; (e0
<generate_stream_from_string+0xe0>)
4604 mov r4, r0
4479 add r1, pc
f7ff fffe bl 0 <is_prefixed_with>
- b170 cbz r0, 84 <generate_stream_from_string+0x84>
+ b170 cbz r0, 80 <generate_stream_from_string+0x80>
1d20 adds r0, r4, #4
f7ff fffe bl 0 <skip_whitespace>
7805 ldrb r5, [r0, #0]
- b94d cbnz r5, 84 <generate_stream_from_string+0x84>
- 4b17 ldr r3, [pc, #92] ; (d0
<generate_stream_from_string+0xd0>)
+ b94d cbnz r5, 80 <generate_stream_from_string+0x80>
+ 4b1d ldr r3, [pc, #116] ; (e4
<generate_stream_from_string+0xe4>)
f859 0003 ldr.w r0, [r9, r3]
f7ff fffe bl 0 <generate_stream_from_string>
f7ff fffe bl 0 <fflush_all>
4628 mov r0, r5
f7ff fffe bl 0 <_exit>
- 4b13 ldr r3, [pc, #76] ; (d4
<generate_stream_from_string+0xd4>)
+ 4b19 ldr r3, [pc, #100] ; (e8
<generate_stream_from_string+0xe8>)
2200 movs r2, #0
f859 3003 ldr.w r3, [r9, r3]
- a807 add r0, sp, #28
- 681b ldr r3, [r3, #0]
4621 mov r1, r4
- 6edb ldr r3, [r3, #108] ; 0x6c
+ 6818 ldr r0, [r3, #0]
+ 6ec3 ldr r3, [r0, #108] ; 0x6c
9200 str r2, [sp, #0]
681a ldr r2, [r3, #0]
+ 3080 adds r0, #128 ; 0x80
3304 adds r3, #4
f7ff fffe bl 0 <generate_stream_from_string>
9b05 ldr r3, [sp, #20]
6018 str r0, [r3, #0]
- 4b08 ldr r3, [pc, #32] ; (c4
<generate_stream_from_string+0xc4>)
- 9809 ldr r0, [sp, #36] ; 0x24
+ 4b0e ldr r3, [pc, #56] ; (d8
<generate_stream_from_string+0xd8>)
f859 2003 ldr.w r2, [r9, r3]
- 4b0b ldr r3, [pc, #44] ; (d8
<generate_stream_from_string+0xd8>)
+ 4b11 ldr r3, [pc, #68] ; (ec
<generate_stream_from_string+0xec>)
444b add r3, r9
6013 str r3, [r2, #0]
+ 4b0f ldr r3, [pc, #60] ; (e8
<generate_stream_from_string+0xe8>)
+ f859 3003 ldr.w r3, [r9, r3]
+ 681c ldr r4, [r3, #0]
+ f8d4 0080 ldr.w r0, [r4, #128] ; 0x80
+ f7ff fffe bl 0 <free>
+ 2300 movs r3, #0
+ f8c4 3080 str.w r3, [r4, #128] ; 0x80
+ 9807 ldr r0, [sp, #28]
+ f8dd 900c ldr.w r9, [sp, #12]
f7ff fffe bl 0 <close>
- 9808 ldr r0, [sp, #32]
+ 9806 ldr r0, [sp, #24]
f8dd 900c ldr.w r9, [sp, #12]
- b00b add sp, #44 ; 0x2c
+ b009 add sp, #36 ; 0x24
bd30 pop {r4, r5, pc}
- bf00 nop
- 00000098 .word 0x00000098
+ 000000b0 .word 0x000000b0
- 0000006a .word 0x0000006a
+ 00000082 .word 0x00000082
As you can see from the assembly code, with the volatile char **to_free on
the stack, it doesn't even bother to call free()!
That explains why I thought the stack was being overwritten when I was
using hardware watchpoints on the stack variable. It does allocate space
for the variable, it does initialize it to 0, but since it doesn't bother
to call free(), it certainly never reads it back. So my watchpoint caught
the entry into generate_stream_from_string, and the saving of the pointer
from the xmalloc(), but the next access to that stack location was not
inside the generate_stream_from_string() parent call, which I assumed it
would be, so I mistakenly thought somehow some errant thing took over the
stack.
If I turn on my leak hunt code, it does make a call to free() because it's
a different function that also gets the line number etc. But that assembly
code also does not read the variable value from the stack memory, it just
inserts a 0.
On Mon, Aug 4, 2025 at 10:38 PM Harry Eaton <[email protected]> wrote:
>
> >Because the parent attempted to free that memory
>> >by calling free(to_free), but the child (as I explained)
>> >did not store the pointer in need of freeing into to_free.
>> >to_free remained NULL, and parent didn't free anything.
>>
>> For the record, the above is not quite correct. The child absolutely does
> store the pointer into the stack variable of the parent.
> What GCC has optimized out is fetching that pointer from "to_free" in
> order to pass it to the free() call.
> Instead the compiler simply inserts NULL instead of actually loading the
> variable from memory.
>
> Attached is a gdb session log that conclusively proves that that is what
> happens.
>
> But it is still an optimization problem and declaring to_free as volatile
> should solve it.
>
>
_______________________________________________
busybox mailing list
[email protected]
https://lists.busybox.net/mailman/listinfo/busybox