Repository: mmtk/mmtk-core
Pull request #1269: Add a function to print object info from the space for debugging
Author: login: qinsoon
Created: 2025-02-04T03:32:03Z | Merged: 2025-05-14T04:13:29Z

This PR adds a non-mangled function to print object information for debugging. I also added a section in the doc about the function.


Diff:
diff --git a/docs/userguide/src/SUMMARY.md b/docs/userguide/src/SUMMARY.md
index c376788ae7..e938172c73 100644
--- a/docs/userguide/src/SUMMARY.md
+++ b/docs/userguide/src/SUMMARY.md
@@ -35,6 +35,7 @@
         - [Next Steps](portingguide/howto/next_steps.md)
     - [Debugging Tips](portingguide/debugging/prefix.md)
         - [Enabling Debug Assertions](portingguide/debugging/assertions.md)
+        - [Print Object Info](portingguide/debugging/print_obj_info.md)
     - [Performance Tuning](portingguide/perf_tuning/prefix.md)
         - [Link Time Optimization](portingguide/perf_tuning/lto.md)
         - [Optimizing Allocation](portingguide/perf_tuning/alloc.md)
diff --git a/docs/userguide/src/portingguide/debugging/print_obj_info.md b/docs/userguide/src/portingguide/debugging/print_obj_info.md
new file mode 100644
index 0000000000..4eae7e06a3
--- /dev/null
+++ b/docs/userguide/src/portingguide/debugging/print_obj_info.md
@@ -0,0 +1,30 @@
+# Print MMTk Object Information
+
+MMTk provides a utility function to print object information for debugging, `crate::mmtk::mmtk_debug_print_object`.
+The function is marked as `#[no_mangle]`, making it suitable to be used in a debugger.
+
+The following example shows how to use the function to print MMTk's object metadata before and after a single GC in `rr`.
+
+Set up break points before and after a GC.
+
+```console
+(rr) b stop_all_mutators
+Breakpoint 1 at 0x767cba8f74e8 (14 locations)
+(rr) b resume_mutators
+Breakpoint 2 at 0x767cba8f8908
+```
+
+When the program stops, call `mmtk_debug_print_object`. We might need to
+set the language context in the debugger to `C` when we stop in a Rust frame.
+Then call `mmtk_debug_print_object` with the interested object.
+
+```console
+(rr) set language c
+(rr) call mmtk_debug_print_object_info(0x200fffed9f0)
+In immix:
+marked = false
+line marked = false
+block state = Unmarked
+forwarding bits = 0, forwarding pointer = None
+vo bit = true
+```
diff --git a/src/mmtk.rs b/src/mmtk.rs
index e0a355108f..f99e487e1b 100644
--- a/src/mmtk.rs
+++ b/src/mmtk.rs
@@ -597,3 +597,32 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+/// A non-mangled function to print object information for debugging purposes. This function can be directly
+/// called from a debugger.
+#[no_mangle]
+pub fn mmtk_debug_print_object(object: crate::util::ObjectReference) {
+    // If the address is unmapped, we cannot access its metadata. Just quit.
+    if !object.to_raw_address().is_mapped() {
+        println!("{} is not mapped in MMTk", object);
+        return;
+    }
+
+    // If the address is not aligned to the object reference size, it is not an object reference.
+    if !object
+        .to_raw_address()
+        .is_aligned_to(crate::util::ObjectReference::ALIGNMENT)
+    {
+        println!(
+            "{} is not properly aligned. It is not an object reference.",
+            object
+        );
+    }
+
+    // Forward to the space
+    let sft = SFT_MAP.get_checked(object.to_raw_address());
+    // Print the space name
+    println!("In {}:", sft.name());
+    // Print object information
+    sft.debug_print_object_info(object);
+}
diff --git a/src/policy/copyspace.rs b/src/policy/copyspace.rs
index bbc66ccd4d..7067f65d43 100644
--- a/src/policy/copyspace.rs
+++ b/src/policy/copyspace.rs
@@ -100,6 +100,11 @@ impl<VM: VMBinding> SFT for CopySpace<VM> {
         let worker = worker.into_mut::<VM>();
         self.trace_object(queue, object, self.common.copy, worker)
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        object_forwarding::debug_print_object_forwarding_info::<VM>(object);
+        self.common.debug_print_object_global_info(object);
+    }
 }
 
 impl<VM: VMBinding> Space<VM> for CopySpace<VM> {
diff --git a/src/policy/immix/immixspace.rs b/src/policy/immix/immixspace.rs
index 7b0429406b..08acafffed 100644
--- a/src/policy/immix/immixspace.rs
+++ b/src/policy/immix/immixspace.rs
@@ -158,6 +158,20 @@ impl<VM: VMBinding> SFT for ImmixSpace<VM> {
     ) -> ObjectReference {
         panic!("We do not use SFT to trace objects for Immix. sft_trace_object() cannot be used.")
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        println!("marked  = {}", self.is_marked(object));
+        println!(
+            "line marked = {}",
+            Line::from_unaligned_address(object.to_raw_address()).is_marked(self.mark_state)
+        );
+        println!(
+            "block state = {:?}",
+            Block::from_unaligned_address(object.to_raw_address()).get_state()
+        );
+        object_forwarding::debug_print_object_forwarding_info::<VM>(object);
+        self.common.debug_print_object_global_info(object);
+    }
 }
 
 impl<VM: VMBinding> Space<VM> for ImmixSpace<VM> {
diff --git a/src/policy/largeobjectspace.rs b/src/policy/largeobjectspace.rs
index d9f274d479..a9346e010c 100644
--- a/src/policy/largeobjectspace.rs
+++ b/src/policy/largeobjectspace.rs
@@ -145,6 +145,12 @@ impl<VM: VMBinding> SFT for LargeObjectSpace<VM> {
     ) -> ObjectReference {
         self.trace_object(queue, object)
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        println!("marked = {}", self.test_mark_bit(object, self.mark_state));
+        println!("nursery = {}", self.is_in_nursery(object));
+        self.common.debug_print_object_global_info(object);
+    }
 }
 
 impl<VM: VMBinding> Space<VM> for LargeObjectSpace<VM> {
diff --git a/src/policy/markcompactspace.rs b/src/policy/markcompactspace.rs
index d7ca0c0745..2f3689f70b 100644
--- a/src/policy/markcompactspace.rs
+++ b/src/policy/markcompactspace.rs
@@ -102,6 +102,15 @@ impl<VM: VMBinding> SFT for MarkCompactSpace<VM> {
         // Depending on which trace it is, we should manually call either trace_mark or trace_forward.
         panic!("sft_trace_object() cannot be used with mark compact space")
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        println!("marked = {}", MarkCompactSpace::<VM>::is_marked(object));
+        println!(
+            "head forwarding pointer = {:?}",
+            MarkCompactSpace::<VM>::get_header_forwarding_pointer(object)
+        );
+        self.common.debug_print_object_global_info(object);
+    }
 }
 
 impl<VM: VMBinding> Space<VM> for MarkCompactSpace<VM> {
diff --git a/src/policy/sft.rs b/src/policy/sft.rs
index c443b48ad3..052a145bf3 100644
--- a/src/policy/sft.rs
+++ b/src/policy/sft.rs
@@ -101,6 +101,12 @@ pub trait SFT {
         object: ObjectReference,
         worker: GCWorkerMutRef,
     ) -> ObjectReference;
+
+    /// Print debug info for the object. The implementer should print one line at a time so in case of an unexpected error,
+    /// we still print something.
+    fn debug_print_object_info(&self, _object: ObjectReference) {
+        println!("This policy does not implement debug_print_object_info.");
+    }
 }
 
 // Create erased VM refs for these types that will be used in `sft_trace_object()`.
diff --git a/src/policy/space.rs b/src/policy/space.rs
index d300efbbf9..e44874fe5b 100644
--- a/src/policy/space.rs
+++ b/src/policy/space.rs
@@ -723,6 +723,22 @@ impl<VM: VMBinding> CommonSpace<VM> {
             },
         }
     }
+
+    pub(crate) fn debug_print_object_global_info(&self, object: ObjectReference) {
+        #[cfg(feature = "vo_bit")]
+        println!(
+            "vo bit = {}",
+            crate::util::metadata::vo_bit::is_vo_bit_set(object)
+        );
+        if self.needs_log_bit {
+            use crate::vm::object_model::ObjectModel;
+            use std::sync::atomic::Ordering;
+            println!(
+                "log bit = {}",
+                VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.is_unlogged::<VM>(object, Ordering::Relaxed),
+            );
+        }
+    }
 }
 
 fn get_frac_available(frac: f32) -> usize {
diff --git a/src/util/object_forwarding.rs b/src/util/object_forwarding.rs
index 4b3f2411e9..76c0e93715 100644
--- a/src/util/object_forwarding.rs
+++ b/src/util/object_forwarding.rs
@@ -233,3 +233,16 @@ pub(super) fn forwarding_bits_offset_in_forwarding_pointer<VM: VMBinding>() -> O
 pub(super) fn forwarding_bits_offset_in_forwarding_pointer<VM: VMBinding>() -> Option<isize> {
     unimplemented!()
 }
+
+pub(crate) fn debug_print_object_forwarding_info<VM: VMBinding>(object: ObjectReference) {
+    let forwarding_bits = get_forwarding_status::<VM>(object);
+    println!(
+        "forwarding bits = {:?}, forwarding pointer = {:?}",
+        forwarding_bits,
+        if state_is_forwarded_or_being_forwarded(forwarding_bits) {
+            Some(read_forwarding_pointer::<VM>(object))
+        } else {
+            None
+        }
+    )
+}
diff --git a/src/vm/tests/mock_tests/mock_test_debug_get_object_info.rs b/src/vm/tests/mock_tests/mock_test_debug_get_object_info.rs
new file mode 100644
index 0000000000..bf54939442
--- /dev/null
+++ b/src/vm/tests/mock_tests/mock_test_debug_get_object_info.rs
@@ -0,0 +1,15 @@
+// GITHUB-CI: MMTK_PLAN=all
+
+use super::mock_test_prelude::*;
+
+#[test]
+pub fn debug_print_object_info() {
+    with_mockvm(
+        default_setup,
+        || {
+            let fixture = SingleObject::create();
+            crate::mmtk::mmtk_debug_print_object(fixture.objref);
+        },
+        no_cleanup,
+    )
+}
diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs
index 946d861da1..af00a83422 100644
--- a/src/vm/tests/mock_tests/mod.rs
+++ b/src/vm/tests/mock_tests/mod.rs
@@ -35,6 +35,7 @@ mod mock_test_allocator_info;
 mod mock_test_barrier_slow_path_assertion;
 #[cfg(feature = "is_mmtk_object")]
 mod mock_test_conservatism;
+mod mock_test_debug_get_object_info;
 #[cfg(target_os = "linux")]
 mod mock_test_handle_mmap_conflict;
 mod mock_test_handle_mmap_oom;


Reviews:
user: login: wks
body: I have some high-level suggestions.

-   The argument of `mmtk_debug_print_object_info` should be an `Address` instead of `ObjectReference` because GDB and rr users may pass any number to that function.
-   Because the object may not be valid, we need to check if the side metadata are mapped before we attempt to read any side metadata.
-   Even with all the checking in place, it is still better to print one piece of information at a time using multiple `println!` statements.  If any step panics (or cause SIGSEGV if it reads unmapped metadata), the user can still see partial information.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T05:38:04Z
user: login: qinsoon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T06:37:36Z
user: login: qinsoon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T06:38:45Z
user: login: qinsoon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T06:39:15Z
user: login: qinsoon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T06:40:38Z
user: login: wks
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T11:45:42Z
user: login: qinsoon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T00:45:47Z
user: login: qinsoon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T00:47:00Z
user: login: wks
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-14T01:59:28Z
user: login: wks
body: LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-14T02:49:46Z

Review comments:
diff_hunk: @@ -597,3 +597,13 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+#[no_mangle]
+pub fn mmtk_debug_print_object_info(object: crate::util::ObjectReference) {
path: src/mmtk.rs
user: login: wks
body: I think this function is intended to be called from C or GDB.  In this case, the user may just supply an arbitrary number and want it to be used as an `ObjectReference`.  But `ObjectReference` is not `repr(C)`, and it has validity requirements (non-zero and word-aligned).  I think we can change this to `Address` and validate it inside this function.

```suggestion
pub fn mmtk_debug_print_object_info(object_addr: crate::util::Address) {
    let Some(object) = ObjectReference::from_raw_address(object_addr) else {
        println!("{object_addr} is not a valid object reference");
        return;
    };
```
created_at: 2025-05-13T05:14:19Z
author_association: COLLABORATOR
diff_hunk: @@ -597,3 +597,13 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+#[no_mangle]
+pub fn mmtk_debug_print_object_info(object: crate::util::ObjectReference) {
+    println!(
+        "{}",
+        SFT_MAP
+            .get_checked(object.to_raw_address())
+            .debug_get_object_info(object)
+    )
path: src/mmtk.rs
user: login: wks
body: Instead of printing the result here, I suggest we let `debug_get_object_info` gradually print more and more information.  The reason is that when the user uses GDB or rr to debug a runtime, chance is high that the heap may have already been corrupted, and the metadata may have been inconsistent.  In this case, one of the steps inside `debug_get_object_info` may go wrong and panic before it returns a human-readable string.  Instead, if we let it gradually print more and more info, it can make the best effort even if it fails to finish normally
created_at: 2025-05-13T05:18:09Z
author_association: COLLABORATOR
diff_hunk: @@ -100,6 +100,15 @@ impl<VM: VMBinding> SFT for CopySpace<VM> {
         let worker = worker.into_mut::<VM>();
         self.trace_object(queue, object, self.common.copy, worker)
     }
+
+    fn debug_get_object_info(&self, object: ObjectReference) -> String {
+        format!(
+            "{}: {:?}, {:?}",
+            self.name(),
+            object_forwarding::debug_get_object_forwarding_info::<VM>(object),
+            crate::policy::sft::debug_get_object_global_info(object)
+        )
path: src/policy/copyspace.rs
user: login: wks
body: The same is here.  We can put three `println!` statements here and print three pieces of information separately.
created_at: 2025-05-13T05:19:30Z
author_association: COLLABORATOR
diff_hunk: @@ -101,6 +101,27 @@ pub trait SFT {
         object: ObjectReference,
         worker: GCWorkerMutRef,
     ) -> ObjectReference;
+
+    fn debug_get_object_info(&self, object: ObjectReference) -> String {
+        format!(
+            "{}: This policy does not implement debug_get_object_info. Global info: {}",
+            self.name(),
+            crate::policy::sft::debug_get_object_global_info(object)
+        )
+    }
+}
+
+pub(crate) fn debug_get_object_global_info(_object: ObjectReference) -> String {
+    #[allow(unused_mut)]
+    let mut ret = String::new();
+    #[cfg(feature = "vo_bit")]
+    {
+        ret += &format!(
+            "vo bit = {}",
+            crate::util::metadata::vo_bit::is_vo_bit_set(_object)
path: src/policy/sft.rs
user: login: wks
body: `is_vo_bit_set` doesn't check if the metadata is mapped or not.  We should call `is_mmtk_object` or equivalently `is_vo_bit_set_for_addr` instead.  And we should actually check it in `crate::mmtk::mmtk_debug_print_object_info` when converting the `Address` to `ObjectReference`.
created_at: 2025-05-13T05:24:59Z
author_association: COLLABORATOR
diff_hunk: @@ -233,3 +233,16 @@ pub(super) fn forwarding_bits_offset_in_forwarding_pointer<VM: VMBinding>() -> O
 pub(super) fn forwarding_bits_offset_in_forwarding_pointer<VM: VMBinding>() -> Option<isize> {
     unimplemented!()
 }
+
+pub(crate) fn debug_get_object_forwarding_info<VM: VMBinding>(object: ObjectReference) -> String {
+    let forwarding_bits = get_forwarding_status::<VM>(object);
path: src/util/object_forwarding.rs
user: login: wks
body: Again before actually checking the forwarding information, we should check if the metadata is mapped.  Some VMs (such as CRuby) are using side forwarding bits.  It is possible that `object` is invalid and its metadata is not mapped, yet.
created_at: 2025-05-13T05:26:03Z
author_association: COLLABORATOR
diff_hunk: @@ -597,3 +597,13 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+#[no_mangle]
+pub fn mmtk_debug_print_object_info(object: crate::util::ObjectReference) {
path: src/mmtk.rs
user: login: wks
body: By the way, if the `vo_bit` feature is enabled, we should use the VO bit to convert the object to `ObjectReference`.  But if that fails, we may still "force" converting `object_addr` to an `ObjectReference` to provide some information with "best-effort".
created_at: 2025-05-13T05:32:48Z
author_association: COLLABORATOR
diff_hunk: @@ -597,3 +597,13 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+#[no_mangle]
+pub fn mmtk_debug_print_object_info(object: crate::util::ObjectReference) {
+    println!(
+        "{}",
+        SFT_MAP
+            .get_checked(object.to_raw_address())
+            .debug_get_object_info(object)
+    )
path: src/mmtk.rs
user: login: qinsoon
body: Done. Now each function impl only fetches one piece of metadata at a time, and print one line at a time.
created_at: 2025-05-13T06:37:36Z
author_association: MEMBER
diff_hunk: @@ -101,6 +101,27 @@ pub trait SFT {
         object: ObjectReference,
         worker: GCWorkerMutRef,
     ) -> ObjectReference;
+
+    fn debug_get_object_info(&self, object: ObjectReference) -> String {
+        format!(
+            "{}: This policy does not implement debug_get_object_info. Global info: {}",
+            self.name(),
+            crate::policy::sft::debug_get_object_global_info(object)
+        )
+    }
+}
+
+pub(crate) fn debug_get_object_global_info(_object: ObjectReference) -> String {
+    #[allow(unused_mut)]
+    let mut ret = String::new();
+    #[cfg(feature = "vo_bit")]
+    {
+        ret += &format!(
+            "vo bit = {}",
+            crate::util::metadata::vo_bit::is_vo_bit_set(_object)
path: src/policy/sft.rs
user: login: qinsoon
body: I added a check to check if the object is mapped or not before forwarding to SFT. If the object is mapped, we can assume all the metadata can be accessed.
created_at: 2025-05-13T06:38:45Z
author_association: MEMBER
diff_hunk: @@ -233,3 +233,16 @@ pub(super) fn forwarding_bits_offset_in_forwarding_pointer<VM: VMBinding>() -> O
 pub(super) fn forwarding_bits_offset_in_forwarding_pointer<VM: VMBinding>() -> Option<isize> {
     unimplemented!()
 }
+
+pub(crate) fn debug_get_object_forwarding_info<VM: VMBinding>(object: ObjectReference) -> String {
+    let forwarding_bits = get_forwarding_status::<VM>(object);
path: src/util/object_forwarding.rs
user: login: qinsoon
body: See above.
created_at: 2025-05-13T06:39:15Z
author_association: MEMBER
diff_hunk: @@ -101,6 +101,27 @@ pub trait SFT {
         object: ObjectReference,
         worker: GCWorkerMutRef,
     ) -> ObjectReference;
+
+    fn debug_get_object_info(&self, object: ObjectReference) -> String {
+        format!(
+            "{}: This policy does not implement debug_get_object_info. Global info: {}",
+            self.name(),
+            crate::policy::sft::debug_get_object_global_info(object)
+        )
+    }
+}
+
+pub(crate) fn debug_get_object_global_info(_object: ObjectReference) -> String {
+    #[allow(unused_mut)]
+    let mut ret = String::new();
+    #[cfg(feature = "vo_bit")]
+    {
+        ret += &format!(
+            "vo bit = {}",
+            crate::util::metadata::vo_bit::is_vo_bit_set(_object)
path: src/policy/sft.rs
user: login: qinsoon
body: If the object is mapped, but some side metadata is not mapped, then it is a bug in MMTk core. We do not expect the function to work properly, and we do not expect the function can help us figure out the bug either.
created_at: 2025-05-13T06:40:38Z
author_association: MEMBER
diff_hunk: @@ -597,3 +597,31 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+/// A non-mangled function to print object information for debugging purposes. This function can be directly
+/// called from a debugger.
+#[no_mangle]
+pub fn mmtk_debug_print_object_info(object: crate::util::ObjectReference) {
+    // If the address is unmapped, we cannot access its metadata. Just quit.
+    assert!(
+        object.to_raw_address().is_mapped(),
+        "{} is not mapped in MMTk",
+        object
+    );
path: src/mmtk.rs
user: login: wks
body: This is an assertion.  If this fails, it will panic.  It is right for normal execution, but this function is for calling in GDB.  For example,

```gdb
(gdb) p (int)abort()

Program received signal SIGABRT, Aborted.
0x00007ffff7e0174c in ?? () from /usr/lib/libc.so.6
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwind-on-signal on".
Evaluation of the expression containing the function
(abort) will be abandoned.
When the function is done executing, GDB will silently stop.
(gdb) 
```

It is better to just use `println!` to print an error message, and return if it is impossible to proceed.
created_at: 2025-05-13T11:35:58Z
author_association: COLLABORATOR
diff_hunk: @@ -100,6 +100,12 @@ impl<VM: VMBinding> SFT for CopySpace<VM> {
         let worker = worker.into_mut::<VM>();
         self.trace_object(queue, object, self.common.copy, worker)
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        println!("In {}", self.name());
+        object_forwarding::debug_print_object_forwarding_info::<VM>(object);
+        crate::policy::sft::debug_print_object_global_info(object);
path: src/policy/copyspace.rs
user: login: wks
body: Every implementation of `debug_print_object_info` starts with `println!("In {}", self.name());` and ends with `debug_print_object_global_info`.  We can hoist both of them into the caller (i.e. `mmtk_debug_print_object_info`).
created_at: 2025-05-13T11:45:31Z
author_association: COLLABORATOR
diff_hunk: @@ -597,3 +597,31 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+/// A non-mangled function to print object information for debugging purposes. This function can be directly
+/// called from a debugger.
+#[no_mangle]
+pub fn mmtk_debug_print_object_info(object: crate::util::ObjectReference) {
+    // If the address is unmapped, we cannot access its metadata. Just quit.
+    assert!(
+        object.to_raw_address().is_mapped(),
+        "{} is not mapped in MMTk",
+        object
+    );
path: src/mmtk.rs
user: login: qinsoon
body: Done.
created_at: 2025-05-14T00:45:47Z
author_association: MEMBER
diff_hunk: @@ -100,6 +100,12 @@ impl<VM: VMBinding> SFT for CopySpace<VM> {
         let worker = worker.into_mut::<VM>();
         self.trace_object(queue, object, self.common.copy, worker)
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        println!("In {}", self.name());
+        object_forwarding::debug_print_object_forwarding_info::<VM>(object);
+        crate::policy::sft::debug_print_object_global_info(object);
path: src/policy/copyspace.rs
user: login: qinsoon
body: I put `println!("In {}", self.name());` to the caller. I realized that we cannot put `debug_print_object_global_info` to the caller, as we need the `VM` parameter which we only have for each space. I moved the function to `CommonSpace`, and adds printing for log bits there.
created_at: 2025-05-14T00:47:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,25 @@
+# Print MMTk Object Information
+
+MMTk provides a utility function to print object information for debugging, `crate::mmtk::mmtk_debug_print_object`.
+The function is marked as `#[no_mangle]`, making it suitable to be used in a debugger.
+
+The following example shows how to use the function to print MMTk's object metadata before and after a single GC in `rr`.
+
+Set up break points before and after a GC.
+
+```console
+(rr) b stop_all_mutators
+Breakpoint 1 at 0x767cba8f74e8 (14 locations)
+(rr) b resume_mutators
+Breakpoint 2 at 0x767cba8f8908
+```
+
+When the program stops, call `mmtk_debug_print_object`. We might need to
+set the language context in the debugger to `C` when we stop in a Rust frame.
+Then call `mmtk_debug_print_object` with the interested object.
+
+```console
+(rr) set language c
+(rr) call mmtk_debug_print_object_info(0x200fffed9f0)
+immix: marked = false, line marked = false, block state = Unmarked, forwarding bits = 0, forwarding pointer = None, vo bit = true
path: docs/userguide/src/portingguide/debugging/print_obj_info.md
user: login: wks
body: As we changed the output format, we should update the documentation, too.
created_at: 2025-05-14T01:55:10Z
author_association: COLLABORATOR
diff_hunk: @@ -597,3 +597,32 @@ impl<VM: VMBinding> MMTK<VM> {
             .initialize_object_metadata(object, false)
     }
 }
+
+/// A non-mangled function to print object information for debugging purposes. This function can be directly
+/// called from a debugger.
+#[no_mangle]
+pub fn mmtk_debug_print_object(object: crate::util::ObjectReference) {
+    // If the address is unmapped, we cannot access its metadata. Just quit.
+    if !object.to_raw_address().is_mapped() {
+        println!("{} is not mapped in MMTk", object);
+        return;
+    }
+
+    // If the address is not aligned to the object reference size, it is not an object reference.
+    if object
+        .to_raw_address()
+        .is_aligned_to(crate::util::ObjectReference::ALIGNMENT)
path: src/mmtk.rs
user: login: wks
body: I think you mean "if it is NOT aligned to `ObjectReference::ALIGNMENT`".

```suggestion
    if !object
        .to_raw_address()
        .is_aligned_to(crate::util::ObjectReference::ALIGNMENT)
```
created_at: 2025-05-14T01:56:11Z
author_association: COLLABORATOR
diff_hunk: @@ -100,6 +100,12 @@ impl<VM: VMBinding> SFT for CopySpace<VM> {
         let worker = worker.into_mut::<VM>();
         self.trace_object(queue, object, self.common.copy, worker)
     }
+
+    fn debug_print_object_info(&self, object: ObjectReference) {
+        println!("In {}", self.name());
path: src/policy/copyspace.rs
user: login: wks
body: These `println!("In {}", self.name());` statements are still there in `copyspace.rs` and other spaces.
created_at: 2025-05-14T01:57:21Z
author_association: COLLABORATOR

PR comments:
user: login: qinsoon
created_at: 2025-05-13T05:54:20Z
body: > The argument of mmtk_debug_print_object_info should be an Address instead of ObjectReference because GDB and rr users may pass any number to that function.

I don't agree with this. We do want the user to give us an object reference (even if it is represented as a `usize` in the debugger). The intention of this function is to print information for **an object** -- we don't want to access object metadata for an arbitrary address.

We can do our best guess in the function to rule out some possible issues, such as unmapped address/metadata, etc. But the function may crash if the heap/metadata is corrupted, or if the argument is invalid. After all this is just a utility function to help debugging.
author_association: MEMBER
user: login: wks
created_at: 2025-05-13T06:11:44Z
body: > > The argument of mmtk_debug_print_object_info should be an Address instead of ObjectReference because GDB and rr users may pass any number to that function.
> 
> I don't agree with this. We do want the user to give us an object reference (even if it is represented as a `usize` in the debugger). The intention of this function is to print information for **an object** -- we don't want to access object metadata for an arbitrary address.
> 
> We can do our best guess in the function to rule out some possible issues, such as unmapped address/metadata, etc. But the function may crash if the heap/metadata is corrupted, or if the argument is invalid. After all this is just a utility function to help debugging.

I mean, the user SHOULD always pass an object reference.  But the user usually cannot verify if the address is valid.  They usually copy an address from a log message or a stack frame info, such as

    #3 0x00007ffff7e77090 in some_function(obj=1073741828) from /xxxx/xxx.so

The user probably don't use a calculator to check if 1073741828 is a multiple of 8 (in fact it is not) before invoking `p mmtk_debug_print_object_info(1073741828)`.  That's why the parameter type is `Address`.  `mmtk_debug_print_object_info` will verify it, and make it an `ObjectReference`.  If the parameter type is already `ObjectReference`, it will silently accept 1073741828 as the value of a `ObjectReference` and pass it to other places that assume it is aligned, and some weird error will occur.


author_association: COLLABORATOR
user: login: qinsoon
created_at: 2025-05-13T06:20:35Z
body: If the user passes an object reference, we print information for the object. If they don't pass an object reference, they get what they get, which may not make any sense at all.

Without VO bit, we cannot tell if an address is an object reference or not. We can make guesses and rule out some least possible cases, but we cannot tell definitely that the address is an object or not. In such a case, we can't access the object metadata in a meaningful way if we are not sure if the address is an object reference.

I think we can just require an object reference. It is the user's responsibility to pass us a valid object reference. However, we do some checks in case that the reference is obviously invalid. 
author_association: MEMBER
user: login: qinsoon
created_at: 2025-05-14T02:12:17Z
body: The above issues are fixed.
author_association: MEMBER