Repository: mozilla/cbindgen (language: rust)

Issue:
Allow access to bindings symbols names
What I'm trying to do:
1. write a Rust library with a C-compatible interface (extern functions, etc)
2. use `cbindgen` to generate a C header, to use the library from C
3. compile a Rust program that depends on the library and links with `-C link-args=-Wl,--export-dynamic-symbol=<regex of symbols to export>` (or another [ldd option](https://man.archlinux.org/man/extra/lld/ld.lld.1.en)), in order to load plugins (written in C) that use the library symbols

For step 3, I need the list of symbols in the C header. I can parse the header or fill this information by hand, but `cbindgen` already has the list! What is missing is a way to access it.

I can see two ways of solving this problem (both may be useful):
1. Modify the `Bindings` impl: add a public API to read the `items` of the bindings (with methods to get the exported name of an item of any type), for instance:
```rs
let bindings = cbindgen::Builder::new().with_crate(crate_dir).with_language(C).generate().unwrap(); // usual generation
let symbols_names: Vec<String> = bindings.items.iter().map(|i| i.export_name()).collect(); // would be awesome if possible
```
2. Add a way to export the symbols in the [linker format](https://stackoverflow.com/a/70555660), which looks like
```
{
   symbol_1;
   symbol_2;
};
```
I will then be able to use `--export-dynamic-symbol-list=<exported file>` in order to export the symbols properly.

If someone guides me a little bit, I could try to implement one of the aforementioned features (or both, who knows?) :slightly_smiling_face:.


Discussion / hints:

Fix patch:
diff --git a/src/bindgen/bindings.rs b/src/bindgen/bindings.rs
index 029cfc66b..7ef85baba 100644
--- a/src/bindgen/bindings.rs
+++ b/src/bindgen/bindings.rs
@@ -7,7 +7,7 @@ use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fs;
 use std::fs::File;
-use std::io::{Read, Write};
+use std::io::{BufWriter, Read, Write};
 use std::path;
 use std::rc::Rc;
 
@@ -129,6 +129,27 @@ impl Bindings {
         fields
     }
 
+    /// Lists the exported symbols that can be dynamically linked, i.e. globals and functions.
+    pub fn dynamic_symbols_names(&self) -> impl Iterator<Item = &str> {
+        use crate::bindgen::ir::Item;
+
+        let function_names = self.functions.iter().map(|f| f.path().name());
+        let global_names = self.globals.iter().map(|g| g.export_name());
+        function_names.chain(global_names)
+    }
+
+    pub fn generate_symfile<P: AsRef<path::Path>>(&self, symfile_path: P) {
+        if let Some(dir) = symfile_path.as_ref().parent() {
+            std::fs::create_dir_all(dir).unwrap();
+        }
+        let mut writer = BufWriter::new(File::create(symfile_path).unwrap());
+        write!(&mut writer, "{{\n").expect("writing symbol file header failed");
+        for symbol in self.dynamic_symbols_names() {
+            write!(&mut writer, "{};\n", symbol).expect("writing symbol failed");
+        }
+        write!(&mut writer, "}};").expect("writing symbol file footer failed");
+    }
+
     pub fn generate_depfile<P: AsRef<path::Path>>(&self, header_path: P, depfile_path: P) {
         if let Some(dir) = depfile_path.as_ref().parent() {
             if !dir.exists() {
diff --git a/src/main.rs b/src/main.rs
index a6a1852c2..f8d9c437a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -298,6 +298,17 @@ fn main() {
                     This option is ignored if `--out` is missing."
                 )
         )
+        .arg(
+            Arg::new("symfile")
+                .value_name("PATH")
+                .long("symfile")
+                .num_args(1)
+                .required(false)
+                .help("Generate a list of symbols at the given Path. This list can be \
+                    given to a linker in order to compile an application that exposes \
+                    dynamic symbols. Useful when creating a plugin system with a C interface."
+                )
+        )
         .get_matches();
 
     if matches.get_flag("verify") && !matches.contains_id("out") {
@@ -343,7 +354,10 @@ fn main() {
                 std::process::exit(2);
             }
             if let Some(depfile) = matches.get_one("depfile") {
-                bindings.generate_depfile(file, depfile)
+                bindings.generate_depfile(file, depfile);
+            }
+            if let Some(symfile) = matches.get_one::<String>("symfile") {
+                bindings.generate_symfile(symfile);
             }
         }
         _ => {


Test patch:
diff --git a/tests/expectations-symbols/abi_string.c.sym b/tests/expectations-symbols/abi_string.c.sym
new file mode 100644
index 000000000..19e73069e
--- /dev/null
+++ b/tests/expectations-symbols/abi_string.c.sym
@@ -0,0 +1,4 @@
+{
+c;
+c_unwind;
+};
diff --git a/tests/expectations-symbols/alias.c.sym b/tests/expectations-symbols/alias.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/alias.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/annotation.c.sym b/tests/expectations-symbols/annotation.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/annotation.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/array.c.sym b/tests/expectations-symbols/array.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/array.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/asserted_cast.c.sym b/tests/expectations-symbols/asserted_cast.c.sym
new file mode 100644
index 000000000..57c772c4a
--- /dev/null
+++ b/tests/expectations-symbols/asserted_cast.c.sym
@@ -0,0 +1,3 @@
+{
+foo;
+};
diff --git a/tests/expectations-symbols/assoc_constant.c.sym b/tests/expectations-symbols/assoc_constant.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/assoc_constant.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/associated_in_body.c.sym b/tests/expectations-symbols/associated_in_body.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/associated_in_body.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/bitfield.c.sym b/tests/expectations-symbols/bitfield.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/bitfield.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/bitflags.c.sym b/tests/expectations-symbols/bitflags.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/bitflags.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/body.c.sym b/tests/expectations-symbols/body.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/body.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/box.c.sym b/tests/expectations-symbols/box.c.sym
new file mode 100644
index 000000000..79a114122
--- /dev/null
+++ b/tests/expectations-symbols/box.c.sym
@@ -0,0 +1,5 @@
+{
+root;
+drop_box;
+drop_box_opt;
+};
diff --git a/tests/expectations-symbols/cdecl.c.sym b/tests/expectations-symbols/cdecl.c.sym
new file mode 100644
index 000000000..b3f50ef4a
--- /dev/null
+++ b/tests/expectations-symbols/cdecl.c.sym
@@ -0,0 +1,4 @@
+{
+O;
+root;
+};
diff --git a/tests/expectations-symbols/cell.c.sym b/tests/expectations-symbols/cell.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/cell.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/cfg.c.sym b/tests/expectations-symbols/cfg.c.sym
new file mode 100644
index 000000000..732af1661
--- /dev/null
+++ b/tests/expectations-symbols/cfg.c.sym
@@ -0,0 +1,5 @@
+{
+root;
+root;
+cond;
+};
diff --git a/tests/expectations-symbols/cfg_2.c.sym b/tests/expectations-symbols/cfg_2.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/cfg_2.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/char.c.sym b/tests/expectations-symbols/char.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/char.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/const_generics.c.sym b/tests/expectations-symbols/const_generics.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/const_generics.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/const_generics_arrayvec.c.sym b/tests/expectations-symbols/const_generics_arrayvec.c.sym
new file mode 100644
index 000000000..f6b2cd601
--- /dev/null
+++ b/tests/expectations-symbols/const_generics_arrayvec.c.sym
@@ -0,0 +1,3 @@
+{
+push;
+};
diff --git a/tests/expectations-symbols/const_generics_bool.c.sym b/tests/expectations-symbols/const_generics_bool.c.sym
new file mode 100644
index 000000000..1bed6d7ae
--- /dev/null
+++ b/tests/expectations-symbols/const_generics_bool.c.sym
@@ -0,0 +1,6 @@
+{
+new_set;
+set_for_each;
+new_map;
+map_for_each;
+};
diff --git a/tests/expectations-symbols/const_generics_byte.c.sym b/tests/expectations-symbols/const_generics_byte.c.sym
new file mode 100644
index 000000000..6a1843f1f
--- /dev/null
+++ b/tests/expectations-symbols/const_generics_byte.c.sym
@@ -0,0 +1,5 @@
+{
+init_parens_parser;
+destroy_parens_parser;
+init_braces_parser;
+};
diff --git a/tests/expectations-symbols/const_generics_char.c.sym b/tests/expectations-symbols/const_generics_char.c.sym
new file mode 100644
index 000000000..5888deefd
--- /dev/null
+++ b/tests/expectations-symbols/const_generics_char.c.sym
@@ -0,0 +1,3 @@
+{
+until_nul;
+};
diff --git a/tests/expectations-symbols/const_generics_constant.c.sym b/tests/expectations-symbols/const_generics_constant.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/const_generics_constant.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/const_generics_thru.c.sym b/tests/expectations-symbols/const_generics_thru.c.sym
new file mode 100644
index 000000000..59344f322
--- /dev/null
+++ b/tests/expectations-symbols/const_generics_thru.c.sym
@@ -0,0 +1,4 @@
+{
+one;
+two;
+};
diff --git a/tests/expectations-symbols/constant.c.sym b/tests/expectations-symbols/constant.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/constant.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/constant_sort_name.c.sym b/tests/expectations-symbols/constant_sort_name.c.sym
new file mode 100644
index 000000000..3f7f900f8
--- /dev/null
+++ b/tests/expectations-symbols/constant_sort_name.c.sym
@@ -0,0 +1,4 @@
+{
+C;
+D;
+};
diff --git a/tests/expectations-symbols/constant_sort_none.c.sym b/tests/expectations-symbols/constant_sort_none.c.sym
new file mode 100644
index 000000000..85430bed2
--- /dev/null
+++ b/tests/expectations-symbols/constant_sort_none.c.sym
@@ -0,0 +1,4 @@
+{
+D;
+C;
+};
diff --git a/tests/expectations-symbols/custom_header.c.sym b/tests/expectations-symbols/custom_header.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/custom_header.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/decl_name_conflicting.c.sym b/tests/expectations-symbols/decl_name_conflicting.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/decl_name_conflicting.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/dep_v2.c.sym b/tests/expectations-symbols/dep_v2.c.sym
new file mode 100644
index 000000000..1f4414931
--- /dev/null
+++ b/tests/expectations-symbols/dep_v2.c.sym
@@ -0,0 +1,3 @@
+{
+get_x;
+};
diff --git a/tests/expectations-symbols/deprecated.c.sym b/tests/expectations-symbols/deprecated.c.sym
new file mode 100644
index 000000000..159198dd4
--- /dev/null
+++ b/tests/expectations-symbols/deprecated.c.sym
@@ -0,0 +1,8 @@
+{
+deprecated_without_note;
+deprecated_without_bracket;
+deprecated_with_note;
+deprecated_with_note_and_since;
+deprecated_with_note_which_requires_to_be_escaped;
+dummy;
+};
diff --git a/tests/expectations-symbols/derive_eq.c.sym b/tests/expectations-symbols/derive_eq.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/derive_eq.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/derive_ostream.c.sym b/tests/expectations-symbols/derive_ostream.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/derive_ostream.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/destructor_and_copy_ctor.c.sym b/tests/expectations-symbols/destructor_and_copy_ctor.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/destructor_and_copy_ctor.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/display_list.c.sym b/tests/expectations-symbols/display_list.c.sym
new file mode 100644
index 000000000..c39f6f8b3
--- /dev/null
+++ b/tests/expectations-symbols/display_list.c.sym
@@ -0,0 +1,3 @@
+{
+push_item;
+};
diff --git a/tests/expectations-symbols/doclength_short.c.sym b/tests/expectations-symbols/doclength_short.c.sym
new file mode 100644
index 000000000..ea28bf2be
--- /dev/null
+++ b/tests/expectations-symbols/doclength_short.c.sym
@@ -0,0 +1,4 @@
+{
+root;
+trunk;
+};
diff --git a/tests/expectations-symbols/docstyle_auto.c.sym b/tests/expectations-symbols/docstyle_auto.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/docstyle_auto.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/docstyle_c99.c.sym b/tests/expectations-symbols/docstyle_c99.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/docstyle_c99.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/docstyle_doxy.c.sym b/tests/expectations-symbols/docstyle_doxy.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/docstyle_doxy.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/documentation.c.sym b/tests/expectations-symbols/documentation.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/documentation.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/documentation_attr.c.sym b/tests/expectations-symbols/documentation_attr.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/documentation_attr.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/enum.c.sym b/tests/expectations-symbols/enum.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/enum.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/enum_discriminant.c.sym b/tests/expectations-symbols/enum_discriminant.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/enum_discriminant.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/enum_self.c.sym b/tests/expectations-symbols/enum_self.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/enum_self.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/euclid.c.sym b/tests/expectations-symbols/euclid.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/euclid.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/exclude_generic_monomorph.c.sym b/tests/expectations-symbols/exclude_generic_monomorph.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/exclude_generic_monomorph.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/expand.c.sym b/tests/expectations-symbols/expand.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/expand.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/expand_default_features.c.sym b/tests/expectations-symbols/expand_default_features.c.sym
new file mode 100644
index 000000000..67cb8bbe7
--- /dev/null
+++ b/tests/expectations-symbols/expand_default_features.c.sym
@@ -0,0 +1,4 @@
+{
+extra_debug_fn;
+root;
+};
diff --git a/tests/expectations-symbols/expand_dep.c.sym b/tests/expectations-symbols/expand_dep.c.sym
new file mode 100644
index 000000000..1f4414931
--- /dev/null
+++ b/tests/expectations-symbols/expand_dep.c.sym
@@ -0,0 +1,3 @@
+{
+get_x;
+};
diff --git a/tests/expectations-symbols/expand_dep_v2.c.sym b/tests/expectations-symbols/expand_dep_v2.c.sym
new file mode 100644
index 000000000..1f4414931
--- /dev/null
+++ b/tests/expectations-symbols/expand_dep_v2.c.sym
@@ -0,0 +1,3 @@
+{
+get_x;
+};
diff --git a/tests/expectations-symbols/expand_features.c.sym b/tests/expectations-symbols/expand_features.c.sym
new file mode 100644
index 000000000..70a323c35
--- /dev/null
+++ b/tests/expectations-symbols/expand_features.c.sym
@@ -0,0 +1,5 @@
+{
+extra_debug_fn;
+cbindgen;
+root;
+};
diff --git a/tests/expectations-symbols/expand_no_default_features.c.sym b/tests/expectations-symbols/expand_no_default_features.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/expand_no_default_features.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/export_name.c.sym b/tests/expectations-symbols/export_name.c.sym
new file mode 100644
index 000000000..3698cd3d3
--- /dev/null
+++ b/tests/expectations-symbols/export_name.c.sym
@@ -0,0 +1,3 @@
+{
+do_the_thing_with_export_name;
+};
diff --git a/tests/expectations-symbols/extern.c.sym b/tests/expectations-symbols/extern.c.sym
new file mode 100644
index 000000000..eb1bbe714
--- /dev/null
+++ b/tests/expectations-symbols/extern.c.sym
@@ -0,0 +1,4 @@
+{
+foo;
+bar;
+};
diff --git a/tests/expectations-symbols/extern_2.c.sym b/tests/expectations-symbols/extern_2.c.sym
new file mode 100644
index 000000000..8638cd5a5
--- /dev/null
+++ b/tests/expectations-symbols/extern_2.c.sym
@@ -0,0 +1,4 @@
+{
+first;
+second;
+};
diff --git a/tests/expectations-symbols/external_workspace_child.c.sym b/tests/expectations-symbols/external_workspace_child.c.sym
new file mode 100644
index 000000000..0cb41d3fe
--- /dev/null
+++ b/tests/expectations-symbols/external_workspace_child.c.sym
@@ -0,0 +1,3 @@
+{
+consume_ext;
+};
diff --git a/tests/expectations-symbols/fns.c.sym b/tests/expectations-symbols/fns.c.sym
new file mode 100644
index 000000000..f299d02d3
--- /dev/null
+++ b/tests/expectations-symbols/fns.c.sym
@@ -0,0 +1,4 @@
+{
+root;
+no_return;
+};
diff --git a/tests/expectations-symbols/forward_declaration.c.sym b/tests/expectations-symbols/forward_declaration.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/forward_declaration.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/function_args.c.sym b/tests/expectations-symbols/function_args.c.sym
new file mode 100644
index 000000000..361aca4ad
--- /dev/null
+++ b/tests/expectations-symbols/function_args.c.sym
@@ -0,0 +1,5 @@
+{
+unnamed;
+pointer_test;
+print_from_rust;
+};
diff --git a/tests/expectations-symbols/function_noreturn.c.sym b/tests/expectations-symbols/function_noreturn.c.sym
new file mode 100644
index 000000000..c38fe7d9e
--- /dev/null
+++ b/tests/expectations-symbols/function_noreturn.c.sym
@@ -0,0 +1,4 @@
+{
+loop_forever;
+normal_return;
+};
diff --git a/tests/expectations-symbols/function_ptr.c.sym b/tests/expectations-symbols/function_ptr.c.sym
new file mode 100644
index 000000000..25aa6ab49
--- /dev/null
+++ b/tests/expectations-symbols/function_ptr.c.sym
@@ -0,0 +1,3 @@
+{
+my_function;
+};
diff --git a/tests/expectations-symbols/function_sort_name.c.sym b/tests/expectations-symbols/function_sort_name.c.sym
new file mode 100644
index 000000000..b4badb861
--- /dev/null
+++ b/tests/expectations-symbols/function_sort_name.c.sym
@@ -0,0 +1,6 @@
+{
+A;
+B;
+C;
+D;
+};
diff --git a/tests/expectations-symbols/function_sort_none.c.sym b/tests/expectations-symbols/function_sort_none.c.sym
new file mode 100644
index 000000000..38ad212b2
--- /dev/null
+++ b/tests/expectations-symbols/function_sort_none.c.sym
@@ -0,0 +1,6 @@
+{
+C;
+B;
+D;
+A;
+};
diff --git a/tests/expectations-symbols/generic_pointer.c.sym b/tests/expectations-symbols/generic_pointer.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/generic_pointer.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/global_variable.c.sym b/tests/expectations-symbols/global_variable.c.sym
new file mode 100644
index 000000000..b64e8969c
--- /dev/null
+++ b/tests/expectations-symbols/global_variable.c.sym
@@ -0,0 +1,4 @@
+{
+MUT_GLOBAL_ARRAY;
+CONST_GLOBAL_ARRAY;
+};
diff --git a/tests/expectations-symbols/ignore.c.sym b/tests/expectations-symbols/ignore.c.sym
new file mode 100644
index 000000000..dff03068a
--- /dev/null
+++ b/tests/expectations-symbols/ignore.c.sym
@@ -0,0 +1,3 @@
+{
+no_ignore_root;
+};
diff --git a/tests/expectations-symbols/include_guard.c.sym b/tests/expectations-symbols/include_guard.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/include_guard.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/inner_mod.c.sym b/tests/expectations-symbols/inner_mod.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/inner_mod.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/item_types.c.sym b/tests/expectations-symbols/item_types.c.sym
new file mode 100644
index 000000000..f6fcdc99f
--- /dev/null
+++ b/tests/expectations-symbols/item_types.c.sym
@@ -0,0 +1,3 @@
+{
+;
+};
diff --git a/tests/expectations-symbols/item_types_renamed.c.sym b/tests/expectations-symbols/item_types_renamed.c.sym
new file mode 100644
index 000000000..f6fcdc99f
--- /dev/null
+++ b/tests/expectations-symbols/item_types_renamed.c.sym
@@ -0,0 +1,3 @@
+{
+;
+};
diff --git a/tests/expectations-symbols/lifetime_arg.c.sym b/tests/expectations-symbols/lifetime_arg.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/lifetime_arg.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/linestyle_cr.c.sym b/tests/expectations-symbols/linestyle_cr.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/linestyle_cr.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/linestyle_crlf.c.sym b/tests/expectations-symbols/linestyle_crlf.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/linestyle_crlf.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/linestyle_lf.c.sym b/tests/expectations-symbols/linestyle_lf.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/linestyle_lf.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/mangle.c.sym b/tests/expectations-symbols/mangle.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/mangle.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/manuallydrop.c.sym b/tests/expectations-symbols/manuallydrop.c.sym
new file mode 100644
index 000000000..2d62f6ada
--- /dev/null
+++ b/tests/expectations-symbols/manuallydrop.c.sym
@@ -0,0 +1,4 @@
+{
+root;
+take;
+};
diff --git a/tests/expectations-symbols/maybeuninit.c.sym b/tests/expectations-symbols/maybeuninit.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/maybeuninit.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/mod_2015.c.sym b/tests/expectations-symbols/mod_2015.c.sym
new file mode 100644
index 000000000..bbc2e6756
--- /dev/null
+++ b/tests/expectations-symbols/mod_2015.c.sym
@@ -0,0 +1,4 @@
+{
+export_me;
+from_really_nested_mod;
+};
diff --git a/tests/expectations-symbols/mod_2018.c.sym b/tests/expectations-symbols/mod_2018.c.sym
new file mode 100644
index 000000000..814cea8ee
--- /dev/null
+++ b/tests/expectations-symbols/mod_2018.c.sym
@@ -0,0 +1,5 @@
+{
+export_me;
+export_me_2;
+from_really_nested_mod;
+};
diff --git a/tests/expectations-symbols/mod_attr.c.sym b/tests/expectations-symbols/mod_attr.c.sym
new file mode 100644
index 000000000..eb1bbe714
--- /dev/null
+++ b/tests/expectations-symbols/mod_attr.c.sym
@@ -0,0 +1,4 @@
+{
+foo;
+bar;
+};
diff --git a/tests/expectations-symbols/mod_path.c.sym b/tests/expectations-symbols/mod_path.c.sym
new file mode 100644
index 000000000..441241ba2
--- /dev/null
+++ b/tests/expectations-symbols/mod_path.c.sym
@@ -0,0 +1,3 @@
+{
+export_me;
+};
diff --git a/tests/expectations-symbols/monomorph_1.c.sym b/tests/expectations-symbols/monomorph_1.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/monomorph_1.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/monomorph_2.c.sym b/tests/expectations-symbols/monomorph_2.c.sym
new file mode 100644
index 000000000..eb1bbe714
--- /dev/null
+++ b/tests/expectations-symbols/monomorph_2.c.sym
@@ -0,0 +1,4 @@
+{
+foo;
+bar;
+};
diff --git a/tests/expectations-symbols/monomorph_3.c.sym b/tests/expectations-symbols/monomorph_3.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/monomorph_3.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/must_use.c.sym b/tests/expectations-symbols/must_use.c.sym
new file mode 100644
index 000000000..2b1a7af14
--- /dev/null
+++ b/tests/expectations-symbols/must_use.c.sym
@@ -0,0 +1,3 @@
+{
+maybe_consume;
+};
diff --git a/tests/expectations-symbols/namespace_constant.c.sym b/tests/expectations-symbols/namespace_constant.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/namespace_constant.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/namespaces_constant.c.sym b/tests/expectations-symbols/namespaces_constant.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/namespaces_constant.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/no_includes.c.sym b/tests/expectations-symbols/no_includes.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/no_includes.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/non_pub_extern.c.sym b/tests/expectations-symbols/non_pub_extern.c.sym
new file mode 100644
index 000000000..ce9b8673b
--- /dev/null
+++ b/tests/expectations-symbols/non_pub_extern.c.sym
@@ -0,0 +1,6 @@
+{
+first;
+renamed;
+FIRST;
+RENAMED;
+};
diff --git a/tests/expectations-symbols/nonnull.c.sym b/tests/expectations-symbols/nonnull.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/nonnull.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/nonnull_attribute.c.sym b/tests/expectations-symbols/nonnull_attribute.c.sym
new file mode 100644
index 000000000..78a53d315
--- /dev/null
+++ b/tests/expectations-symbols/nonnull_attribute.c.sym
@@ -0,0 +1,10 @@
+{
+value_arg;
+mutltiple_args;
+ref_arg;
+mut_ref_arg;
+optional_ref_arg;
+optional_mut_ref_arg;
+nullable_const_ptr;
+nullable_mut_ptr;
+};
diff --git a/tests/expectations-symbols/nonzero.c.sym b/tests/expectations-symbols/nonzero.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/nonzero.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/opaque.c.sym b/tests/expectations-symbols/opaque.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/opaque.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/pin.c.sym b/tests/expectations-symbols/pin.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/pin.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/pragma_once.c.sym b/tests/expectations-symbols/pragma_once.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/pragma_once.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/prefix.c.sym b/tests/expectations-symbols/prefix.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/prefix.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/prefixed_struct_literal.c.sym b/tests/expectations-symbols/prefixed_struct_literal.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/prefixed_struct_literal.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/prefixed_struct_literal_deep.c.sym b/tests/expectations-symbols/prefixed_struct_literal_deep.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/prefixed_struct_literal_deep.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/ptrs_as_arrays.c.sym b/tests/expectations-symbols/ptrs_as_arrays.c.sym
new file mode 100644
index 000000000..de7efd434
--- /dev/null
+++ b/tests/expectations-symbols/ptrs_as_arrays.c.sym
@@ -0,0 +1,7 @@
+{
+ptr_as_array;
+ptr_as_array1;
+ptr_as_array2;
+ptr_as_array_wrong_syntax;
+ptr_as_array_unnamed;
+};
diff --git a/tests/expectations-symbols/raw_ident.c.sym b/tests/expectations-symbols/raw_ident.c.sym
new file mode 100644
index 000000000..ea76f34b7
--- /dev/null
+++ b/tests/expectations-symbols/raw_ident.c.sym
@@ -0,0 +1,4 @@
+{
+fn;
+STATIC;
+};
diff --git a/tests/expectations-symbols/raw_lines.c.sym b/tests/expectations-symbols/raw_lines.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/raw_lines.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/rename.c.sym b/tests/expectations-symbols/rename.c.sym
new file mode 100644
index 000000000..8a247ae6f
--- /dev/null
+++ b/tests/expectations-symbols/rename.c.sym
@@ -0,0 +1,4 @@
+{
+root;
+G;
+};
diff --git a/tests/expectations-symbols/rename_case.c.sym b/tests/expectations-symbols/rename_case.c.sym
new file mode 100644
index 000000000..1bacd14c0
--- /dev/null
+++ b/tests/expectations-symbols/rename_case.c.sym
@@ -0,0 +1,7 @@
+{
+test_camel_case;
+test_pascal_case;
+test_snake_case;
+test_screaming_snake_case;
+test_gecko_case;
+};
diff --git a/tests/expectations-symbols/rename_crate.c.sym b/tests/expectations-symbols/rename_crate.c.sym
new file mode 100644
index 000000000..256a2894a
--- /dev/null
+++ b/tests/expectations-symbols/rename_crate.c.sym
@@ -0,0 +1,5 @@
+{
+root;
+renamed_func;
+no_extern_func;
+};
diff --git a/tests/expectations-symbols/renaming_overrides_prefixing.c.sym b/tests/expectations-symbols/renaming_overrides_prefixing.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/renaming_overrides_prefixing.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/reserved.c.sym b/tests/expectations-symbols/reserved.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/reserved.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/sentinel.c.sym b/tests/expectations-symbols/sentinel.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/sentinel.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/simplify_option_ptr.c.sym b/tests/expectations-symbols/simplify_option_ptr.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/simplify_option_ptr.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/size_types.c.sym b/tests/expectations-symbols/size_types.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/size_types.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/static.c.sym b/tests/expectations-symbols/static.c.sym
new file mode 100644
index 000000000..53daac495
--- /dev/null
+++ b/tests/expectations-symbols/static.c.sym
@@ -0,0 +1,6 @@
+{
+root;
+NUMBER;
+FOO;
+BAR;
+};
diff --git a/tests/expectations-symbols/std_lib.c.sym b/tests/expectations-symbols/std_lib.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/std_lib.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/struct.c.sym b/tests/expectations-symbols/struct.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/struct.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/struct_literal.c.sym b/tests/expectations-symbols/struct_literal.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/struct_literal.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/struct_literal_order.c.sym b/tests/expectations-symbols/struct_literal_order.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/struct_literal_order.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/struct_self.c.sym b/tests/expectations-symbols/struct_self.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/struct_self.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/swift_name.c.sym b/tests/expectations-symbols/swift_name.c.sym
new file mode 100644
index 000000000..5110fbbf8
--- /dev/null
+++ b/tests/expectations-symbols/swift_name.c.sym
@@ -0,0 +1,21 @@
+{
+rust_print_hello_world;
+SelfTypeTestStruct_should_exist_ref;
+SelfTypeTestStruct_should_exist_ref_mut;
+SelfTypeTestStruct_should_not_exist_box;
+SelfTypeTestStruct_should_not_exist_return_box;
+SelfTypeTestStruct_should_exist_annotated_self;
+SelfTypeTestStruct_should_exist_annotated_mut_self;
+SelfTypeTestStruct_should_exist_annotated_by_name;
+SelfTypeTestStruct_should_exist_annotated_mut_by_name;
+SelfTypeTestStruct_should_exist_unannotated;
+SelfTypeTestStruct_should_exist_mut_unannotated;
+free_function_should_exist_ref;
+free_function_should_exist_ref_mut;
+unnamed_argument;
+free_function_should_not_exist_box;
+free_function_should_exist_annotated_by_name;
+free_function_should_exist_annotated_mut_by_name;
+PointerToOpaque_create;
+PointerToOpaque_sayHello;
+};
diff --git a/tests/expectations-symbols/transform_op.c.sym b/tests/expectations-symbols/transform_op.c.sym
new file mode 100644
index 000000000..57c772c4a
--- /dev/null
+++ b/tests/expectations-symbols/transform_op.c.sym
@@ -0,0 +1,3 @@
+{
+foo;
+};
diff --git a/tests/expectations-symbols/transparent.c.sym b/tests/expectations-symbols/transparent.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/transparent.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/typedef.c.sym b/tests/expectations-symbols/typedef.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/typedef.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/union.c.sym b/tests/expectations-symbols/union.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/union.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/union_self.c.sym b/tests/expectations-symbols/union_self.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/union_self.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/using_namespaces.c.sym b/tests/expectations-symbols/using_namespaces.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/using_namespaces.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/expectations-symbols/va_list.c.sym b/tests/expectations-symbols/va_list.c.sym
new file mode 100644
index 000000000..715658c35
--- /dev/null
+++ b/tests/expectations-symbols/va_list.c.sym
@@ -0,0 +1,4 @@
+{
+va_list_test;
+va_list_test2;
+};
diff --git a/tests/expectations-symbols/workspace.c.sym b/tests/expectations-symbols/workspace.c.sym
new file mode 100644
index 000000000..0cb41d3fe
--- /dev/null
+++ b/tests/expectations-symbols/workspace.c.sym
@@ -0,0 +1,3 @@
+{
+consume_ext;
+};
diff --git a/tests/expectations-symbols/zst.c.sym b/tests/expectations-symbols/zst.c.sym
new file mode 100644
index 000000000..8c89234f5
--- /dev/null
+++ b/tests/expectations-symbols/zst.c.sym
@@ -0,0 +1,3 @@
+{
+root;
+};
diff --git a/tests/tests.rs b/tests/tests.rs
index eebabefbe..56fce8b39 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1,6 +1,7 @@
 extern crate cbindgen;
 
 use cbindgen::*;
+use tempfile::NamedTempFile;
 use std::collections::HashSet;
 use std::fs::File;
 use std::io::Read;
@@ -21,6 +22,12 @@ fn style_str(style: Style) -> &'static str {
     }
 }
 
+struct CBindgenOutput {
+    bindings_content: Vec<u8>,
+    depfile_content: Option<String>,
+    symfile_content: Option<String>,
+}
+
 fn run_cbindgen(
     path: &Path,
     output: Option<&Path>,
@@ -29,20 +36,30 @@ fn run_cbindgen(
     style: Option<Style>,
     generate_depfile: bool,
     package_version: bool,
-) -> (Vec<u8>, Option<String>) {
+    generate_symfile: bool,
+) -> CBindgenOutput {
     assert!(
-        !(output.is_none() && generate_depfile),
-        "generating a depfile requires outputting to a path"
+        !output.is_none() || !(generate_depfile || generate_symfile),
+        "generating a depfile or symfile requires outputting to a path"
     );
     let program = Path::new(CBINDGEN_PATH);
     let mut command = Command::new(program);
     if let Some(output) = output {
         command.arg("--output").arg(output);
     }
-    let cbindgen_depfile = if generate_depfile {
-        let depfile = tempfile::NamedTempFile::new().unwrap();
-        command.arg("--depfile").arg(depfile.path());
-        Some(depfile)
+    
+    let depfile = if generate_depfile {
+        let tmp = tempfile::NamedTempFile::new().unwrap();
+        command.arg("--depfile").arg(tmp.path());
+        Some(tmp)
+    } else {
+        None
+    };
+    
+    let symfile = if generate_symfile {
+        let tmp = tempfile::NamedTempFile::new().unwrap();
+        command.arg("--symfile").arg(tmp.path());
+        Some(tmp)
     } else {
         None
     };
@@ -86,7 +103,7 @@ fn run_cbindgen(
         str::from_utf8(&cbindgen_output.stderr).unwrap_or_default()
     );
 
-    let bindings = if let Some(output_path) = output {
+    let bindings_content = if let Some(output_path) = output {
         let mut bindings = Vec::new();
         // Ignore errors here, we have assertions on the expected output later.
         let _ = File::open(output_path).map(|mut file| {
@@ -97,18 +114,17 @@ fn run_cbindgen(
         cbindgen_output.stdout
     };
 
-    let depfile_contents = if let Some(mut depfile) = cbindgen_depfile {
-        let mut raw = Vec::new();
-        depfile.read_to_end(&mut raw).unwrap();
-        Some(
-            str::from_utf8(raw.as_slice())
-                .expect("Invalid encoding encountered in depfile")
-                .into(),
-        )
-    } else {
-        None
-    };
-    (bindings, depfile_contents)
+    fn read_to_string(f: NamedTempFile) -> String {
+        std::fs::read_to_string(&f).expect(&format!("Failed to read file as String: {:?}", f))
+    }
+    let depfile_content = depfile.map(read_to_string);
+    let symfile_content = symfile.map(read_to_string);
+
+    CBindgenOutput {
+        bindings_content,
+        depfile_content,
+        symfile_content,
+    }
 }
 
 fn compile(
@@ -208,11 +224,17 @@ fn run_compile_test(
     style: Option<Style>,
     cbindgen_outputs: &mut HashSet<Vec<u8>>,
     package_version: bool,
+    generate_symfile: bool
 ) {
     let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
     let tests_path = Path::new(&crate_dir).join("tests");
     let mut generated_file = tests_path.join("expectations");
+    let mut generated_symfile = tests_path.join("expectations-symbols");
     fs::create_dir_all(&generated_file).unwrap();
+    fs::create_dir_all(&generated_symfile).unwrap();
+    
+    let verify = env::var_os("CBINDGEN_TEST_VERIFY").is_some();
+    let no_compile = env::var_os("CBINDGEN_TEST_NO_COMPILE").is_some();
 
     let style_ext = style
         // Cython is sensitive to dots, so we can't include any dots.
@@ -236,10 +258,12 @@ fn run_compile_test(
 
     let source_file =
         format!("{}{}{}", name, style_ext, lang_ext).replace(SKIP_WARNING_AS_ERROR_SUFFIX, "");
+    let symbols_file = format!("{source_file}.sym");
 
     generated_file.push(source_file);
+    generated_symfile.push(symbols_file);
 
-    let (output_file, generate_depfile) = if env::var_os("CBINDGEN_TEST_VERIFY").is_some() {
+    let (output_file, generate_depfile) = if verify {
         (None, false)
     } else {
         (
@@ -249,7 +273,7 @@ fn run_compile_test(
         )
     };
 
-    let (cbindgen_output, depfile_contents) = run_cbindgen(
+    let CBindgenOutput { bindings_content, depfile_content, symfile_content } = run_cbindgen(
         path,
         output_file,
         language,
@@ -257,9 +281,10 @@ fn run_compile_test(
         style,
         generate_depfile,
         package_version,
+        true
     );
     if generate_depfile {
-        let depfile = depfile_contents.expect("No depfile generated");
+        let depfile = depfile_content.expect("No depfile generated");
         assert!(!depfile.is_empty());
         let mut rules = depfile.split(':');
         let target = rules.next().expect("No target found");
@@ -275,27 +300,36 @@ fn run_compile_test(
         assert_eq!(rules.count(), 0, "More than 1 rule in the depfile");
     }
 
-    if cbindgen_outputs.contains(&cbindgen_output) {
+    if cbindgen_outputs.contains(&bindings_content) {
         // We already generated an identical file previously.
-        if env::var_os("CBINDGEN_TEST_VERIFY").is_some() {
+        if verify {
             assert!(!generated_file.exists());
         } else if generated_file.exists() {
             fs::remove_file(&generated_file).unwrap();
         }
     } else {
-        if env::var_os("CBINDGEN_TEST_VERIFY").is_some() {
-            use std::str::from_utf8;
-            let prev_cbindgen_output = fs::read(&generated_file).unwrap();
-            let cbindgen_output = from_utf8(&cbindgen_output).unwrap();
-            let prev_cbindgen_output = from_utf8(&prev_cbindgen_output).unwrap();
-            assert_eq!(prev_cbindgen_output, cbindgen_output);
+        if verify {
+            // Compare cbindgen output to expected (existing on disk) output.
+            let prev_cbindgen_bindings = fs::read(&generated_file).unwrap();
+            assert_eq!(bindings_content, prev_cbindgen_bindings);
+            
+            if generate_symfile {
+                let symbols = symfile_content.expect("No symfile generated");
+                let prev_cbindgen_symbols = fs::read(&generated_symfile).unwrap();
+                let prev_symbols = String::from_utf8(prev_cbindgen_symbols).unwrap();
+                assert_eq!(symbols, prev_symbols);
+            }
         } else {
-            fs::write(&generated_file, &cbindgen_output).unwrap();
+            fs::write(&generated_file, &bindings_content).expect("Failed to write generated bindings.");
+            if generate_symfile {
+                let symbols = symfile_content.expect("No symfile generated");
+                fs::write(&generated_symfile, &symbols).expect("Failed to write generated symbols.");
+            }
         }
 
-        cbindgen_outputs.insert(cbindgen_output);
+        cbindgen_outputs.insert(bindings_content);
 
-        if env::var_os("CBINDGEN_TEST_NO_COMPILE").is_some() {
+        if no_compile {
             return;
         }
 
@@ -333,6 +367,9 @@ fn test_file(name: &'static str, filename: &'static str) {
     let mut cbindgen_outputs = HashSet::new();
     for cpp_compat in &[true, false] {
         for style in &[Style::Type, Style::Tag, Style::Both] {
+             // We only need to generate the symfile once,
+             // it should not change with the different options.
+            let generate_symfile = !cpp_compat && *style == Style::Type;
             run_compile_test(
                 name,
                 test,
@@ -342,6 +379,7 @@ fn test_file(name: &'static str, filename: &'static str) {
                 Some(*style),
                 &mut cbindgen_outputs,
                 false,
+                generate_symfile,
             );
         }
     }
@@ -355,6 +393,7 @@ fn test_file(name: &'static str, filename: &'static str) {
         None,
         &mut HashSet::new(),
         false,
+        /* generate_symfile = */ false
     );
 
     // `Style::Both` should be identical to `Style::Tag` for Cython.
@@ -369,6 +408,7 @@ fn test_file(name: &'static str, filename: &'static str) {
             Some(*style),
             &mut cbindgen_outputs,
             false,
+            /* generate_symfile = */ false
         );
     }
 }