[PATCH] gdb: consider null terminator for length arguments of value_cstring calls
Pedro Alves
pedro@palves.net
Wed Jul 14 10:25:41 GMT 2021
Adding Philippe as $_gdb_setting original author.
On 2021-07-13 7:44 p.m., Simon Marchi wrote:
> On 2021-07-13 1:07 p.m., Pedro Alves wrote:
>> On 2021-07-13 4:31 p.m., Simon Marchi via Gdb-patches wrote:
> Ohh, thanks. The changes I needed to do in the existing tests should
> have made me think about that. I liked the inferior call approach,
> because it makes the inferior program crash, so it's an obvious bug. By
> just printing the string elements, it may not be as clear cut that it's
> a bug. As long as the values stay in GDB, they don't cause a crash (or
> at least, I didn't find a way to cause a crash).
>
> But if there's not doubt that null-terminating these strings is what we
> want, your approach is fine and makes the test a bit simpler. I added a
> little mention of the inferior function call way of reproducing the bug
> in the commit message. See the updated patch below.
>
For C, it makes sence to me to return a null terminated string. Though I wonder
whether the string you get should depend on language? I mean, see this:
(gdb) set language c
(gdb) ptype "foo"
type = char [4]
(gdb) set language ada
(gdb) ptype "foo"
type = array (1 .. 3) of character
Whatever we decide, I think we should update the manual to make it explicit.
>
> creates a struct value of code TYPE_CODE_ARRAY of size 3, which doesn't
> have a null terminator. That does not create a valid C string. It is
> however printed correctly by GDB, because the printing code makes sure
> not to read past the value's length.
>
> A way to expose the bug is to print each element of the created string,
> including the null terminator. Before:
>
> (gdb) set args foo
> (gdb) p $_gdb_setting("args")[3]
> no such vector element
>
> After:
>
> (gdb) set args foo
> (gdb) p $_gdb_setting("args")[3]
> $1 = 0 '\000'
>
> Another perhaps more convincing way of showing the bug is if the string
> value is passed to an inferior function call;
>
> (gdb) print an_inferior_function($_gdb_setting("args))
>
> The space allocate for the string in the inferior will not take into
"The space allocated" ?
Such an inferior call in Ada (and other non-terminated string languages) will now
be passing an unexpected \0 to the called function, of course. I wonder
whether that's why Philippe didn't add the \0 in the first place, given his
Ada bias? :-)
> account a null terminator (with the string "foo", 3 bytes will be
> allocated). If the inferior tries to read the string until the null
> terminator, it will read past the allocated buffer. Compiling the
> inferior with AddressSanitizer makes that bad access obvious.
>
> I found a few calls to value_cstring that were wrong, I fixed them
> all, all exercised by the test.
>
> The change in guile/scm-math.c maybe requires a bit of explanation.
> According to the doc of gdbscm_scm_to_string, if don't pass a length
"if we don't" ?
> pointer, we get back a null-terminated string. If we pass a length
> pointer, we get a non-null-terminated string, but we get the length
> separately. Trying to pass "len + 1" to value_cstring would lead to GDB
> reading past the allocated buffer, that is exactly of length `len`. So
> instead, don't pass a length pointer and use strlen on the result.
> +load_lib "trace-support.exp"
> +load_lib "gdb-guile.exp"
> +
> +standard_testfile
> +
> +if {[build_executable "failed to prepare" $testfile $srcfile ]} {
> + return
> +}
> +
> +# Check that the string contained in the convenienced variable $v is
convenienced -> convenience
> +# EXPECTED_STR.
> +#
> +# In particular, check that the null terminator is there and that we can't
> +# access a character past the end of the string.
> +
> +proc check_v_string { expected_str } {
> +# Test with string values made by $_gdb_setting & co.
> +
> +proc_with_prefix test_setting { } {
> + clean_restart
> +
> + # This is an internal GDB implementation detail, but the variable backing a
> + # string setting starts as nullptr (unless explicitly initialized at startup).
> + # When assigning an empty value, the variable then points to an empty string.
> + # Test both cases, as it triggers different code paths (in addition to a
> + # non-empty value).
The paranthesis remark in this last sentence gave me pause, as it sounded like
you were saying that the different code paths result in a non-empty value.
I'd suggest instead:
# Test both cases, as it triggers different code paths, in addition to testing a
# non-empty value.
> + #
> + # Use "set trace-user" and "maintenance set test-settings string" as they are
> + # both not initialized at startup.
> + with_test_prefix "user setting" {
> + with_test_prefix "not set" {
> + foreach_with_prefix conv_func {$_gdb_setting $_gdb_setting_str} {
> + gdb_test_no_output "set \$v = ${conv_func}(\"trace-user\")"
> + check_v_string ""
> + }
> + }
> +# Test with a string value created by make-value in Guile.
> +
> +proc_with_prefix test_guile_value { } {
> + clean_restart
> +
> + if {[skip_guile_tests]} {
> + untested "skipping test_guile_value"
> + return
> + }
> +
> + # We can't set a convenience var from Guile, but we can append to history.
> + # Do that, then transfer to a convenience var with a CLI command.
> + gdb_test_no_output "guile (use-modules (gdb))"
> + gdb_test_multiple "guile (history-append! (make-value \"foo\"))" "make value" {
> + -re -wrap "($::decimal)" {
> + set histnum $expect_out(1,string)
set histnum to something before gdb_test_multiple, to avoid ending up with an
unset tcl variable error if this gdb_test_multiple fails.
> + }
> + }
> +
> + gdb_test_no_output "set \$v = \$$histnum"
> + check_v_string "foo"
> +}
> +
More information about the Gdb-patches
mailing list