Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FreeBSD support #63

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Conversation

michael-yuji
Copy link

@michael-yuji michael-yuji commented Jun 4, 2022

This PR contains changes to support to FreeBSD.

* Add a register_probes!() macro, which insert a noop call to a extern function, which argument referencing the set_dtrace_probes section, this prevents the probes from being gc by lld

  • Enable standard backend for FreeBSD if on nightly or if link-dead-code is set.
  • Perform null check on dli_fname and dli_sname as they can be null
    on some platforms.
  • Enable some existing linker workaround on FreeBSD
  • Changes to support FreeBSD specific ioctl
    • Added FreeBSD specific fields to dof_helper
    • Use FreeBSD specific ioctl value

Currently support FreeBSD targets with nightly toolchain, as well as stable toolchain if link-dead-code is set in RUSTFLAGS

There is a linker related issue that make the FreeBSD support preliminary
and limited that crate consumers on FreeBSD need to build binary with
-C link-dead-code=yes

Without this workaround , the linker will omit some symbols and complain
about __start_set_dtrace_probes not found.

A nightly feature, #[used(linker)] is available to fix the issue of linker
omitting __(start|stop)_set_dtrace_probes; It however does not
prevent the linker throwing the probes generated by the provider macros
away. The -C link-dead-code=yes is therefore currently necessary
regardless of the availability of used_with_arg

On some implementation of `dladdr`, the `dli_sname` and `dli_fname`
can be null even if the `dladdr` itself succeed.
FreeBSD implemented DTrace fairly standardly in compare to Illumos.

1. build.rs
   This patch enable FreeBSD to be treated as a "standard" implementation
   with no linker supported in the usdt build.rs.

2. The values to issue ioctl to dtrace helper are different.
   2.1. The cmd value on FreeBSD is derived the C macro
        `_IOWR('z', 3, dot_helper_t)`;  which is unfolded and the value
        is inlined in this patch.
   2.2 The struct dof_helper is also different, in FreeBSD there are
       two extra fields: `dofhp_pid` and `dofhp_gen`.

3. Linker Issue
   Like Illumos, without `FORCE_LOAD` line the linker will omit the symbols
   (even with "-C link-dead-code"); therefore the line also needed to activate
   on FreeBSD.

   There are however still issues in linker that make the FreeBSD support
   preliminary, on FreeBSD the crate consumer need to build with
   "-C link-dead-code", or otherwise the probes will thrown away by the linker.
Provide a macro version of register_probes!() enables a explicit reference to
the set_dtrace_probes section from the crate consumer, and prevent lld from gc
the section. This trick along with the unstable "used(linker)" feature enable
use on FreeBSD target with nightly toolchain without explicit "link-dead-code"

Since the "used_with_arg" is available only on nightly, a patch is applied on
usdt-impl/build.rs such that standard backend is enabled only for FreeBSD
targets that are either on nightly or have link-dead-code explicitly passed to
the linker.
@michael-yuji michael-yuji changed the title Add preliminary FreeBSD support Add FreeBSD support Jun 6, 2022
@bnaecker
Copy link
Collaborator

bnaecker commented Jun 6, 2022

Hi @michael-yuji, thanks for sending this patch! It's good to see broader platform support.

In general the patch looks fine, however I have a few questions and concerns about the details. I'll comment on specific lines as appropriate, but overall it seems like there are several contortions required to prevent the FreeBSD linker from throwing the probe section into the trash. I think these can be simplified, but I don't have a FreeBSD system to test that on. Hopefully we can iterate together.

Last, would you mind including some details on how you tested the code? Output showing some of the example or integration test probes firing would be excellent. Thanks!

usdt-impl/build.rs Outdated Show resolved Hide resolved
usdt-impl/build.rs Outdated Show resolved Hide resolved
usdt/src/lib.rs Outdated Show resolved Hide resolved
@michael-yuji
Copy link
Author

@bnaecker I have been testing mostly with a dummy project implemented some probes right now, however, I do just find out some issues regarding my patch, that when probes are defined outside the mod main lives, lld is still able to throw out the probes.

Regarding of unit tests there are a few issue I am having, the main one is that unlike on Illumos, dladdr does not able to extract the current function names and friends, attached is a result of does-it-work

running 1 test
test tests::test_does_it_work ... FAILED

failures:

---- tests::test_does_it_work stdout ----
   ID   PROVIDER            MODULE                          FUNCTION NAME
85654 doesit94425 does_it_work-733877c2fa8bfdc9                        ?0x128af37 work

	Probe Description Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Types
		args[0]: uint8_t
		args[1]: char *


thread 'tests::test_does_it_work' panicked at 'Mangled function name appears incorrect: ?0x128af37', tests/does-it-work/src/main.rs:94:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::test_does_it_work

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
`` 

@ahl
Copy link
Collaborator

ahl commented Jun 6, 2022

@bnaecker seems like you have the details well in hand, but I wanted to add that it would be good to add CI for FreeBSD if we can either using a built-in action or buildomat... somehow...

Turns out we do not need all the hacks to get FreeBSD linker working.
GNU and llvm both support the "R" flag for sections, which prevent
them from gc.

This make the ugly register_probes! hack obsolete so it can be removed.
No one deserve to have it in their codebase.

https://lists.freebsd.org/archives/freebsd-hackers/2022-June/001191.html
@michael-yuji
Copy link
Author

@ahl @bnaecker Turns out I am a joke and the fix for FreeBSD is quite trivial, seems like recent LLVM and GNU both added support for the "R" section flag, which prevents the section from GC. Therefore the horrible macro hack is not needed at all.

Still need used_with_arg to prevent __(start|stop)_set_dtrace_probes from missing, however.

Reference:
https://lists.freebsd.org/archives/freebsd-hackers/2022-June/001191.html

unit test results except test_compile_errors:

test tests::test_does_it_work ... FAILED

failures:

---- tests::test_does_it_work stdout ----
   ID   PROVIDER            MODULE                          FUNCTION NAME
85654 doesit21421 does_it_work-ec75ae5d51b02d06                        ?0x10b4909 work

	Probe Description Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Types
		args[0]: uint8_t
		args[1]: char *


thread 'tests::test_does_it_work' panicked at 'Mangled function name appears incorrect: ?0x10b4909', tests/does-it-work/src/main.rs:94:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::test_does_it_work

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
    Finished test [unoptimized + debuginfo] target(s) in 0.12s
     Running unittests src/main.rs (target/debug/deps/argument_types-3e47aa3fb9f42b47)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/compile_errors-3946579ff3e35045)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/does_it_work-ec75ae5d51b02d06)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/dof-ee37c50d83ec6a43)

running 1 test
test ser::test::test_padding ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/dtrace_parser-aa5be91b87495e32)

running 37 tests
test tests::test_basic_tokens::case_3 ... ok
test tests::test_bad_basic_token - should panic ... ok
test tests::test_basic_tokens::case_1 ... ok
test tests::test_basic_tokens::case_4 ... ok
test tests::test_basic_tokens::case_6 ... ok
test tests::test_data_type_enum::case_01 ... ok
test tests::test_basic_tokens::case_5 ... ok
test tests::test_basic_tokens::case_2 ... ok
test tests::test_data_type_enum::case_02 ... ok
test tests::test_data_type_conversion ... ok
test tests::test_basic_tokens::case_7 ... ok
test tests::test_data_type_enum::case_03 ... ok
test tests::test_data_type_enum::case_04 ... ok
test tests::test_data_type_enum::case_05 ... ok
test tests::test_comment_provider ... ok
test tests::test_data_type_enum::case_07 ... ok
test tests::test_data_type_enum::case_06 ... ok
test tests::test_data_type_enum::case_10 ... ok
test tests::test_data_type_enum::case_11 ... ok
test tests::test_data_type_enum::case_08 ... ok
test tests::test_data_type_enum::case_09 ... ok
test tests::test_data_type_enum::case_13 ... ok
test tests::test_data_type_enum::case_12 ... ok
test tests::test_data_type_enum::case_15 ... ok
test tests::test_data_type_enum::case_14 ... ok
test tests::test_data_type_enum::case_16 ... ok
test tests::test_data_type_enum::case_17 ... ok
test tests::test_data_types ... ok
test tests::test_basic_provider ... ok
test tests::test_identifier ... ok
test tests::test_null_provider ... ok
test tests::test_probe ... ok
test tests::test_probe_struct_parse ... ok
test tests::test_pragma_provider ... ok
test tests::test_provider_struct ... ok
test tests::test_two_providers ... ok
test tests::test_file_struct ... ok

test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/dusty-25bf72e11a140ff9)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/empty-457f2d06cbc03ba7)

running 1 test
test test::test_main ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/fake_cmd-0876ad23a5a11590)

running 1 test
test test::test_main ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/fake_lib-b7392c3f8f88aea3)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/modules-1fa27ec5d7a593c6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/probe_test_attr-ec93d5109236da25)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/probe_test_build-f2e60a9240c763dd)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/probe_test_macro-078de5e7ba89b97b)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/rename-e52f9fa993216f61)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/rename_builder-2516d87a789cc891)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/test_json-3952c817cc5613ba)

running 1 test
test tests::test_json_support ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.03s

     Running unittests src/main.rs (target/debug/deps/test_unique_id-ba924155b99a5305)

running 1 test
test tests::test_unique_ids ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.24s

     Running unittests src/lib.rs (target/debug/deps/usdt-4639f33e2fbde32a)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/usdt_attr_macro-cbf97a7e9c22db03)

running 18 tests
test tests::test_data_type_from_path ... ok
test tests::test_is_simple_type ... ok
test tests::test_parse_probe_argument_native::case_2 ... ok
test tests::test_parse_probe_argument_native::case_3 ... ok
test tests::test_parse_probe_argument_native::case_1 ... ok
test tests::test_parse_probe_argument_native::case_4 ... ok
test tests::test_parse_probe_argument_native::case_5 ... ok
test tests::test_data_type_from_path_panics - should panic ... ok
test tests::test_parse_probe_argument_native::case_7 ... ok
test tests::test_parse_probe_argument_native::case_6 ... ok
test tests::test_parse_probe_argument_span::case_1 ... ok
test tests::test_parse_probe_argument_span::case_2 ... ok
test tests::test_parse_probe_argument_serializable::case_3 ... ok
test tests::test_parse_probe_argument_serializable::case_1 ... ok
test tests::test_parse_probe_argument_serializable::case_2 ... ok
test tests::test_parse_probe_argument_serializable::case_4 ... ok
test tests::test_verify_use_tree ... ok
test tests::test_check_probe_function_signature ... ok

test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/usdt_impl-32e21314cbac76ae)

running 20 tests
test common::tests::test_generate_type_check_empty ... ok
test record::test::test_process_section ... ok
test record::test::test_emit_probe_record_dunders ... ok
test record::test::test_emit_probe_record ... ok
test common::tests::test_asm_type_convert ... ok
test record::test::test_process_section_future_version ... ok
test record::test::test_process_probe_record ... ok
test common::tests::test_generate_type_check_with_string ... ok
test common::tests::test_generate_type_check_with_shared_slice ... ok
test common::tests::test_construct_probe_args ... ok
test record::test::test_re_process_section ... ok
test record::test::test_process_probe_record_long_names ... ok
test test::test_compile_providers_config ... ok
test test::test_probe_to_d_source ... ok
test test::test_unique_id ... ok
test test::test_provider_to_d_source ... ok
test test::test_unique_id_clone ... ok
test common::tests::test_generate_type_check_native ... ok
test common::tests::test_generate_type_check_with_custom_type ... ok
test test::test_data_type ... ok

test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/usdt_macro-984b4a9c5e5eec56)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/usdt_tests_common-482927fb349e680c)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/zero_arg_probe-4c18eb482f61b800)

running 1 test
test test::test_main ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests compile-errors

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests dof

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests dtrace-parser

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests fake-lib

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt

running 7 tests
test src/lib.rs - (line 145) ... ignored
test src/lib.rs - (line 160) ... ignored
test src/lib.rs - (line 179) ... ignored
test src/lib.rs - (line 243) ... ignored
test src/lib.rs - (line 44) ... ignored
test src/lib.rs - (line 57) ... ignored
test src/lib.rs - (line 92) ... ignored

test result: ok. 0 passed; 0 failed; 7 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt-attr-macro

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt-impl

running 2 tests
test src/lib.rs - UniqueId (line 301) ... ignored
test src/lib.rs - UniqueId (line 339) - compile fail ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.11s

   Doc-tests usdt-macro

running 4 tests
test src/lib.rs - dtrace_provider (line 32) ... ignored
test src/lib.rs - dtrace_provider (line 41) ... ignored
test src/lib.rs - dtrace_provider (line 53) ... ignored
test src/lib.rs - dtrace_provider (line 65) ... ignored

test result: ok. 0 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt-tests-common

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

@bnaecker
Copy link
Collaborator

bnaecker commented Jun 7, 2022

Thanks for the testing notes @michael-yuji, that's very good to see things are mostly passing! And it's also great to hear that we can avoid the macro and public API change. Using the section flag and / or the unstable #[used(linker)] attribute if supported both seem like better approaches.

It's a bit surprising the does-it-work test is failing. As you noted, that relies on dladdr to find the function containing the probe's address. It's true that dladdr may fill dli_sname to NULL, though I've never seen dli_fname == NULL. No platforms that I've used indicate that should be possible based on the man pages (macOS, illumos, Linux). FreeBSD may be different in this regard.

I'm still surprised that the address cannot be resolved to a symbol, especially in an unoptimized, debug build. Do you actually see the expected function containing the probe in the symbol table of the binary? For reference, here's how I can see that on my machine:

bnaecker@feldspar : ~/usdt $ cargo t -- does --nocapture
    Finished test [unoptimized + debuginfo] target(s) in 0.04s
     Running unittests (target/debug/deps/argument_types-835cdcabe3d483e8)
     Running unittests (target/debug/deps/does_it_work-e0be16543f85d0b6)

<SNIP>

running 1 test
   ID   PROVIDER            MODULE                          FUNCTION NAME
89494 doesit2953 does_it_work-e0be16543f85d0b6 _ZN12does_it_work8run_test17hfefac3e354dbfbb0E work

        Probe Description Attributes
                Identifier Names: Internal
                Data Semantics:   Internal
                Dependency Class: Unknown

        Argument Attributes
                Identifier Names: Internal
                Data Semantics:   Internal
                Dependency Class: Unknown

        Argument Types
                args[0]: uint8_t
                args[1]: char *


test tests::test_does_it_work ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s

<SNIP>

That's showing the full output of dtrace -lv on the binary, indicating that the function name is correct. I can see that exact symbol in the table as well:

bnaecker@feldspar : ~/usdt $ nm target/debug/deps/does_it_work-e0be16543f85d0b6 | grep does_it_work8run_test
[7095]  |             9778784|                 444|FUNC |LOCL |2    |14     |_ZN12does_it_work8run_test17hfefac3e354dbfbb0E
[7804]  |             9779232|                  35|FUNC |LOCL |0    |14     |_ZN12does_it_work8run_test28_$u7b$$u7b$closure$u7d$$u7d$17h7ecd26e668047f99E

If the symbol name is in the table, then it's possible that the dladdr approach we have here doesn't work on FreeBSD for some reason. I can't see anything suggesting this should fail in the man page.

@michael-yuji
Copy link
Author

michael-yuji commented Jun 7, 2022

I can see the expected function in the symbol table, however I do realized something in comparing to Illumos. Parameter addr of addr_to_info matches the address, which is not the case on FreeBSD.

FreeBSD

nm -t x ../usdt/target/debug/deps/does_it_work-733877c2fa8bfdc9 | grep does_it_work8run_test
00000000004cf480 t _ZN12does_it_work8run_test17h138985bf45becfc8E
00000000004d3590 t _ZN12does_it_work8run_test28_$u7b$$u7b$closure$u7d$$u7d$17h4e8c5651b5acc520E
addr_to_info: 10b95a9
dli_fname: Some("/usr/home/yuuji/usdt/target/debug/deps/does_it_work-733877c2fa8bfdc9")
dli_fbase: 0x1021000
dli_sname: None
dli_addr: 0x0
   ID   PROVIDER            MODULE                          FUNCTION NAME
85654 doesit86001 does_it_work-733877c2fa8bfdc9                        ?0x10b95a9 work

	Probe Description Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Types
		args[0]: uint8_t
		args[1]: char *

OmniOS:

nm -t x target/debug/deps/does_it_work-b3471be252aa7090 | grep does_it_work8
[5842]  |0x000000000081cf50|0x00000000000001bc|FUNC |LOCL |0x2  |14     |_ZN12does_it_work8run_test17hc0f511bb6bcf75b0E
[5147]  |0x000000000082b470|0x0000000000000023|FUNC |LOCL |0x2  |14     |_ZN12does_it_work8run_test28_$u7b$$u7b$closure$u7d$$u7d$17hb08d6d327c8477a8E
addr: 81cfab
dli_fname: Some("/home/yuuji/usdt/target/debug/deps/does_it_work-b3471be252aa7090")
dli_fbase: 0x400000
dli_sname: Some("_ZN12does_it_work8run_test17hc0f511bb6bcf75b0E")
dli_addr: 0x81cf50
   ID   PROVIDER            MODULE                          FUNCTION NAME
70763 doesit6118 does_it_work-b3471be252aa7090 _ZN12does_it_work8run_test17hc0f511bb6bcf75b0E work

        Probe Description Attributes
                Identifier Names: Internal
                Data Semantics:   Internal
                Dependency Class: Unknown

        Argument Attributes
                Identifier Names: Internal
                Data Semantics:   Internal
                Dependency Class: Unknown

        Argument Types
                args[0]: uint8_t
                args[1]: char *

Copy link
Collaborator

@ahl ahl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good progress; can we get CI?

dof/src/dof_bindings.rs Show resolved Hide resolved
usdt-impl/src/no-linker.rs Show resolved Hide resolved
usdt-impl/src/no-linker.rs Show resolved Hide resolved
Comment on lines 183 to 188
let cmd: u64 = {
const DOFHELPER_SIZE: u64 = std::mem::size_of::<dof::dof_bindings::dof_helper>() as u64;
const IOCPARM_SHIFT: u64 = 13;
const IOCPARM_MASK: u64 = (1 << IOCPARM_SHIFT) - 1;
const IOC_INOUT: u64 = 0xc0000000;
IOC_INOUT | ((DOFHELPER_SIZE & IOCPARM_MASK) << 16) | 0x7a << 8 | 0x3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's either just have the constant as for illumos or let's change illumos to show its work as you have for freebsd -- i.e. let's not have two ways of doing the same thing

Some(CStr::from_ptr(info.dli_sname).to_string_lossy().to_string()),
Some(CStr::from_ptr(info.dli_fname).to_string_lossy().to_string()),
)
unsafe fn to_str_if_not_null(ptr: *const std::os::raw::c_char) -> Option<String> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserves a comment; did you encounter this in the wild?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserves a comment; did you encounter this in the wild?

Yes, on FreeBSD, dli_sname can be NULL even when dladdr succeed, currently this is always the case when using USDT. dli_fname likely never be NULL and I probably should just remove the check.

usdt-impl/src/record.rs Show resolved Hide resolved
@bnaecker
Copy link
Collaborator

bnaecker commented Jun 7, 2022

good progress; can we get CI?

@ahl I've not dug too deeply. There's nothing on GH itself, so we'd have to spin up a buildomat runner. I'll try to dig into that this week.

@michael-yuji
Copy link
Author

michael-yuji commented Jun 9, 2022

@bnaecker I have found a workaround for the dladdr issue on FreeBSD.

The root cause is that dladdr examines only the dynamic symbol table and hence static functions are not visible. See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=134391

The bug report inspired me to look into how backtrace is implemented on FreeBSD. Turns out, libexecinfo implemented backtrace_symbols_* functions to not only use dladdr but also lookup executable for the symbols. Most importantly, it is able to pull the mangled symbol and pathname by the given address, and therefore is used to implement the fix.

Annoyingly, backtrace_symbols_fmt is not part of the libc crate on FreeBSD, so I have to create an extern block.

All tests passed now except for test_compile_errors

FreeBSD dladdr(3M) examine dynamic symbol table only hence never give use the mangled symbol.
Instead, we use `backtrace_symbols_fmt` to give us the symbol.

Handle backtrace_symbols_fmt and ffi a bit more carefully
@michael-yuji
Copy link
Author

@ahl @bnaecker Hi everyone, may I know if there's any update on this / anything I should change?

@bnaecker
Copy link
Collaborator

Thanks for the ping @michael-yuji, and for your patience. I've triggered the CI run to make sure everything works on the main platforms we support (illumos, macOS, and the no-op implementation on Linux). Let's see how that all shakes out and go from there!

Copy link
Collaborator

@bnaecker bnaecker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @michael-yuji! Thanks again for your patience, I'm very sorry this has gotten away from us! I've added a few more comments here, but they're mostly nits. Overall, this looks more or less ready. Once we address these comments here and CI is satisfied, I'd be happy to merge this.

Thanks again!

Some(CStr::from_ptr(info.dli_fname).to_string_lossy().to_string()),
)
}
}
}

// On FreeBSD, dladdr(3M) only examines the dynamic symbol table. Which is pretty useless as it
// will always gives null dli_sname. To workaround this issue, we use `backtrace_symbols_fmt` from
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo here "will always gives" -> "always returns a"

Some(CStr::from_ptr(info.dli_fname).to_string_lossy().to_string()),
)
}
}
}

// On FreeBSD, dladdr(3M) only examines the dynamic symbol table. Which is pretty useless as it
// will always gives null dli_sname. To workaround this issue, we use `backtrace_symbols_fmt` from
// libexecinfo, which internally lookup in the executable to determine the symbol of the given
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo here "internally lookup in the" -> "internally looks in the"

Comment on lines 117 to 118
let lines: Vec<_> = s.lines().collect();
(Some(lines[0].to_string()), Some(lines[1].to_string()))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: You don't need to collect this to a Vec. You can keep it as the Lines iterator and call next(). Something like:

let lines = s.lines();
(Some(lines.next().unwrap().to_string()), Some(lines.next().unwrap().to_string()))

) -> *mut *mut libc::c_char;
}

let addrs = [addr].as_ptr() as *const *mut c_void;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this works. [addr] creates a temporary array, which you then take the address of and store it to addrs. Does that temporary not get dropped right away, invalidating that address? I guess it might be okay, since it's on the stack, but I would feel much more comfortable with a let binding to that array:

let addr_arr = [addr];
let addrs = addr_arr.as_ptr() as *const *mut c_void;

usdt-impl/src/record.rs Show resolved Hide resolved
#[cfg(target_os = "freebsd")]
pub(crate) fn addr_to_info(addr: u64) -> (Option<String>, Option<String>) {
unsafe {
// The libc crate does not have `backtrace_symbos_fmt`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment isn't needed, it's clear we're specifying the native library to link against.

@@ -71,14 +72,54 @@ pub(crate) fn addr_to_info(addr: u64) -> (Option<String>, Option<String>) {
if libc::dladdr(addr as *const c_void, &mut info as *mut _) == 0 {
(None, None)
} else {
// On some non Illumos platfroms dli_sname can be NULL
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two nits: "platforms" is misspelled, and there's an extra space following it.

@bnaecker
Copy link
Collaborator

The failures in CI are due to the tests around error messages we emit when the macros fail. They've always been a bit annoying to maintain. I believe the issue would be resolved by you rebasing on the latest main, which should include this fix that helps stabilize these messages in CI.

@michael-yuji
Copy link
Author

@bnaecker Thank you so much for your time and review. Do you mean rebase to #67?

@bnaecker
Copy link
Collaborator

@michael-yuji I meant an earlier commit actually, but I think was not right. During development, we built tests that verify the errors we produce when compilation fails, for example creating a probe with an unsupported data type. Those were useful initially, but have become a pain to maintain across many toolchain versions. I'm removing them in #67, as you noted. Once that merges, you should be able to rebase onto that. Thanks!

@bnaecker
Copy link
Collaborator

@michael-yuji Thanks for your patience again. I resolved the CI issues causing the error-message checks to fail. You should now be able to rebase onto master and pick those up as well. Once that's done, I can kick off the CI job. Thanks!

@michael-yuji
Copy link
Author

michael-yuji commented Nov 28, 2022

@bnaecker Done, thank you so much!

@bnaecker
Copy link
Collaborator

@michael-yuji Friendly ping on this PR! I'd love to see all your hard work integrated. If you bring this up-to-date with master, I can take another quick look to make sure that all the requested changes have been integrated. I still need to look into getting a FreeBSD running through our CI system. I'll check that out today, and add commits if we can get that in place. If not, I think you posting the output of your tests against the final commit would be acceptable.

@michael-yuji
Copy link
Author

@bnaecker Thanks for following up, I'm a bit busy this week, but I can probably bring it up to date this weekend.

@michael-yuji
Copy link
Author

@bnaecker I have updated the patch and following are the test outputs. 🙌

uname

FreeBSD 14.0-CURRENT #13 main-n261297-61507ae30b90-dirty: Fri Mar  3 06:49:30 PST 2023

The complete cargo test command

RUST_BACKTRACE=1 TRYBUILD=overwrite cargo +nightly test

Output

warning: unused import: `Dl_info`
  --> usdt-impl/src/record.rs:21:20
   |
21 | use libc::{c_void, Dl_info};
   |                    ^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

warning: unused import: `null`
  --> usdt-impl/src/record.rs:29:11
   |
29 |     ptr::{null, null_mut},
   |           ^^^^

warning: `usdt-impl` (lib) generated 2 warnings (run `cargo fix --lib -p usdt-impl` to apply 2 suggestions)
warning: `usdt-impl` (lib test) generated 2 warnings (2 duplicates)
    Finished test [unoptimized + debuginfo] target(s) in 0.38s
     Running unittests src/main.rs (target/debug/deps/argument_types-3822dfef754a62d6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/compile_errors-613756f1a0ff9a44)

running 1 test
warning: unused import: `Dl_info`
  --> /usr/home/yuuji/devel/usdt/usdt-impl/src/record.rs:21:20
   |
21 | use libc::{c_void, Dl_info};
   |                    ^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

warning: unused import: `null`
  --> /usr/home/yuuji/devel/usdt/usdt-impl/src/record.rs:29:11
   |
29 |     ptr::{null, null_mut},
   |           ^^^^

warning: `usdt-impl` (lib) generated 2 warnings (run `cargo fix --lib -p usdt-impl` to apply 2 suggestions)
warning: `usdt-impl` (lib) generated 2 warnings (2 duplicates)
    Checking compile-errors-tests v0.0.0 (/usr/home/yuuji/devel/usdt/target/tests/trybuild/compile-errors)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s


test src/type-mismatch.rs ... ok
test src/unsupported-type.rs ... ok
test src/no-closure.rs ... ok
test src/no-provider-file.rs ... ok
test src/zero-arg-probe-type-check.rs ... ok
test src/different-serializable-type.rs ... ok
test src/relative-import.rs ... ok


test tests::test_compile_errors ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.73s

     Running unittests src/main.rs (target/debug/deps/does_it_work-1274211c3d19efca)

running 1 test
test tests::test_does_it_work ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s

     Running unittests src/lib.rs (target/debug/deps/dof-f2ce2dbc6acaca39)

running 1 test
test ser::test::test_padding ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/dtrace_parser-b5e8e807368ca5c9)

running 37 tests
test tests::test_basic_tokens::case_2 ... ok
test tests::test_basic_tokens::case_1 ... ok
test tests::test_basic_tokens::case_3 ... ok
test tests::test_basic_tokens::case_6 ... ok
test tests::test_basic_tokens::case_4 ... ok
test tests::test_basic_tokens::case_5 ... ok
test tests::test_basic_tokens::case_7 ... ok
test tests::test_data_type_enum::case_02 ... ok
test tests::test_data_type_enum::case_01 ... ok
test tests::test_data_type_enum::case_03 ... ok
test tests::test_data_type_conversion ... ok
test tests::test_data_type_enum::case_05 ... ok
test tests::test_comment_provider ... ok
test tests::test_data_type_enum::case_04 ... ok
test tests::test_data_type_enum::case_09 ... ok
test tests::test_data_type_enum::case_10 ... ok
test tests::test_data_type_enum::case_06 ... ok
test tests::test_data_type_enum::case_08 ... ok
test tests::test_basic_provider ... ok
test tests::test_data_type_enum::case_07 ... ok
test tests::test_data_type_enum::case_12 ... ok
test tests::test_data_type_enum::case_11 ... ok
test tests::test_data_type_enum::case_14 ... ok
test tests::test_data_type_enum::case_13 ... ok
test tests::test_data_type_enum::case_15 ... ok
test tests::test_data_type_enum::case_16 ... ok
test tests::test_data_type_enum::case_17 ... ok
test tests::test_data_types ... ok
test tests::test_null_provider ... ok
test tests::test_identifier ... ok
test tests::test_probe ... ok
test tests::test_pragma_provider ... ok
test tests::test_probe_struct_parse ... ok
test tests::test_provider_struct ... ok
test tests::test_two_providers ... ok
test tests::test_file_struct ... ok
test tests::test_bad_basic_token - should panic ... ok

test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s

     Running unittests src/main.rs (target/debug/deps/dusty-4d9a5dba952efede)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/empty-7ccf4b06c10c6159)

running 1 test
test test::test_main ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

     Running unittests src/main.rs (target/debug/deps/fake_cmd-fd011476e60a8b14)

running 1 test
test test::test_main ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

     Running unittests src/lib.rs (target/debug/deps/fake_lib-1886748ee3d00de8)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/modules-92a1c0c0969e827f)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/probe_test_attr-736e04c8de600335)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/probe_test_build-20e92746b3e70cf6)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/probe_test_macro-489893017ac54e2b)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/rename-4f3c51398c80a6b1)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/rename_builder-e09c8911f0abaa56)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/test_json-2b04342493121bd0)

running 1 test
test tests::test_json_support ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.08s

     Running unittests src/main.rs (target/debug/deps/test_unique_id-37c3158cbc3dbf81)

running 1 test
test tests::test_unique_ids ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.64s

     Running unittests src/lib.rs (target/debug/deps/usdt-07986fab38b26e9a)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/usdt_attr_macro-f6800b505fbb402c)

running 18 tests
test tests::test_is_simple_type ... ok
test tests::test_data_type_from_path ... ok
test tests::test_parse_probe_argument_native::case_1 ... ok
test tests::test_parse_probe_argument_native::case_3 ... ok
test tests::test_parse_probe_argument_native::case_5 ... ok
test tests::test_parse_probe_argument_native::case_4 ... ok
test tests::test_parse_probe_argument_native::case_2 ... ok
test tests::test_parse_probe_argument_native::case_6 ... ok
test tests::test_check_probe_function_signature ... ok
test tests::test_parse_probe_argument_native::case_7 ... ok
test tests::test_parse_probe_argument_serializable::case_1 ... ok
test tests::test_parse_probe_argument_serializable::case_2 ... ok
test tests::test_parse_probe_argument_serializable::case_3 ... ok
test tests::test_parse_probe_argument_span::case_2 ... ok
test tests::test_parse_probe_argument_span::case_1 ... ok
test tests::test_verify_use_tree ... ok
test tests::test_parse_probe_argument_serializable::case_4 ... ok
test tests::test_data_type_from_path_panics - should panic ... ok

test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s

     Running unittests src/lib.rs (target/debug/deps/usdt_impl-c60a222c45607fc5)

running 20 tests
test record::test::test_emit_probe_record ... ok
test common::tests::test_generate_type_check_empty ... ok
test common::tests::test_asm_type_convert ... ok
test common::tests::test_construct_probe_args ... ok
test common::tests::test_generate_type_check_native ... ok
test record::test::test_emit_probe_record_dunders ... ok
test record::test::test_process_section_future_version ... ok
test common::tests::test_generate_type_check_with_custom_type ... ok
test common::tests::test_generate_type_check_with_string ... ok
test common::tests::test_generate_type_check_with_shared_slice ... ok
test test::test_compile_providers_config ... ok
test test::test_probe_to_d_source ... ok
test test::test_unique_id ... ok
test test::test_unique_id_clone ... ok
test test::test_provider_to_d_source ... ok
test test::test_data_type ... ok
test record::test::test_process_probe_record ... ok
test record::test::test_process_probe_record_long_names ... ok
test record::test::test_process_section ... ok
test record::test::test_re_process_section ... ok

test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s

     Running unittests src/lib.rs (target/debug/deps/usdt_macro-e52611059a383cb7)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/lib.rs (target/debug/deps/usdt_tests_common-482927fb349e680c)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/zero_arg_probe-31bb2163c6a65f4e)

running 1 test
test test::test_main ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

   Doc-tests compile-errors

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests dof

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests dtrace-parser

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests fake-lib

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt

running 7 tests
test src/lib.rs - (line 104) ... ignored
test src/lib.rs - (line 157) ... ignored
test src/lib.rs - (line 172) ... ignored
test src/lib.rs - (line 191) ... ignored
test src/lib.rs - (line 58) ... ignored
test src/lib.rs - (line 69) ... ignored
test src/lib.rs - (line 255) ... ok

test result: ok. 1 passed; 0 failed; 6 ignored; 0 measured; 0 filtered out; finished in 0.55s

   Doc-tests usdt-attr-macro

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt-impl

running 2 tests
test src/lib.rs - UniqueId (line 301) ... ignored
test src/lib.rs - UniqueId (line 337) - compile fail ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.17s

   Doc-tests usdt-macro

running 4 tests
test src/lib.rs - dtrace_provider (line 32) ... ignored
test src/lib.rs - dtrace_provider (line 41) ... ignored
test src/lib.rs - dtrace_provider (line 53) ... ignored
test src/lib.rs - dtrace_provider (line 65) ... ignored

test result: ok. 0 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests usdt-tests-common

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

@@ -16,6 +16,7 @@

#![cfg_attr(usdt_need_feat_asm, feature(asm))]
#![cfg_attr(usdt_need_feat_asm_sym, feature(asm_sym))]
#![cfg_attr(usdt_need_feat_used_with_arg, feature(used_with_arg))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I don't know if I realized it before, but this feature is not stable. We've recently been able to support a stable compiler on all platforms, and it would be very nice to keep that. I'm not sure that's possible, so FreeBSD might be the hold-out requiring nightly.

Are you certain that the link-dead-code linker-flag is not sufficient by itself to keep the FreeBSD linker from removing the start/stop symbols?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With stable compiler and link-dead-code, it kinda works empirically under the limited cases I have tried but not that I can comfortably conclude it works (especially when it's failing tests on wrong mangled function name).

Apart from the correctness issue, It also require consumer of the crate to add the linker flag to make it useful.

😞

(edited: attaching the failed test case)


     Running unittests src/main.rs (target/debug/deps/does_it_work-d0fe3ba398374246)

running 1 test
test tests::test_does_it_work ... FAILED

failures:

---- tests::test_does_it_work stdout ----
   ID   PROVIDER            MODULE                          FUNCTION NAME
89108 doesit2045 does_it_work-d0fe3ba398374246 _ZN12does_it_work4main17hfd4a5f4c02c325b6E work

	Probe Description Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Attributes
		Identifier Names: Internal
		Data Semantics:   Internal
		Dependency Class: Unknown

	Argument Types
		args[0]: uint8_t
		args[1]: char *


thread 'tests::test_does_it_work' panicked at 'Mangled function name appears incorrect: _ZN12does_it_work4main17hfd4a5f4c02c325b6E', tests/does-it-work/src/main.rs:94:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::test_does_it_work

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.26s

error: test failed, to rerun pass `-p does-it-work --bin does-it-work`

@CampoDiFiori
Copy link

CampoDiFiori commented Dec 26, 2023

@michael-yuji @bnaecker Hi all, it would be cool to merge this PR one way or another, right? Do I understand correctly that the main issue now is that we either merge this but are forced to use nightly or we keep stable but force FreeBSD users to include link-dead-code flag?

If that's the case, then the latter option seems cool to me, if it counts for anything. Are there other options, maybe? Is there some alternative path for making it work under stable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants