Skip to content

Fix the semihosting SYS_READ return value for partial reads - #4525

Merged
bugadani merged 1 commit into
probe-rs:masterfrom
MaxFreedomPollard:fix/semihosting-read-return-value
Oct 23, 2026
Merged

bugadani merged 1 commit into
probe-rs:masterfrom
MaxFreedomPollard:fix/semihosting-read-return-value

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

ReadRequest::write_buffer_to_target returns the number of bytes read when a read fills only part of the buffer. SYS_READ returns the number of bytes not filled in the buffer (buffer_length - bytes_read), and targets derive the count from that; the semihosting crate, for example, keeps len - not_read bytes. Reading a 10-byte file with a 64-byte buffer therefore handed the target 54 bytes: the 10 from the file and 44 bytes the host never wrote. This hits the last read of most files, and short reads from TCP and UNIX stream handles.

The fix returns len - bytes_read for every read. A full buffer still returns 0 and an empty read still returns len.

I have not compiled or run this locally (no Rust toolchain on this machine). I checked it against the spec and against OpenOCD's semihosting_common.c, which also returns len - result. There is no new test: write_buffer_to_target needs a Core, and the fake core in probe/fake_probe.rs does not keep register writes, so a test could only repeat the subtraction.

🤖 Generated with Claude Code

SYS_READ returns the number of bytes not filled in the buffer. For a
read that filled only part of the buffer, write_buffer_to_target
returned the number of bytes read instead, so the target computed the
wrong count from it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@paoValle paoValle left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The direction is right: ARM semihosting SYS_READ returns the number of bytes not filled, so for a request of len bytes that read n the status must be len - n, and the old len => len as i32 arm returned the filled count instead. The pre-existing assert!(buf.len() <= self.len as usize) also keeps the subtraction from underflowing, which is the thing I would otherwise have flagged.

Three small things:

  1. Nothing pins the new behaviour. semihosting.rs has no test for write_buffer_to_target, and this is exactly the kind of value that regressed once already (inverted for partial reads, correct only for the full and empty cases). Pulling the arithmetic into a pure helper — fn unfilled_bytes(&self, filled: usize) -> i32 — would let you test all four cases without a Core: 0 filled, partial, exactly self.len, and a filled > self.len panic if you keep the assert.

  2. status as i32 is applied to self.len, which comes from the target and is a u32. A request for more than i32::MAX bytes now writes a negative status, which the target reads back as a huge positive value. This shape predates the PR (the 0 => self.len as i32 arm had it too), so it is not a regression, but since the formula now touches it directly a i32::try_from(..).unwrap_or(i32::MAX) with a one-line comment would close it.

  3. The doc comment still reads only "Writes the buffer to the target", while the function's other output — the status register write, derived from the unfilled count — is the thing this PR fixes. One sentence there would save the next reader the trip to the ARM spec.

@bugadani

Copy link
Copy Markdown
Contributor

LLM-generated review comment on an LLM-generated PR. What a world we live in 😔

@bugadani
bugadani added this pull request to the merge queue Oct 23, 2026
Merged via the queue into probe-rs:master with commit 177b2e5 Oct 23, 2026
20 checks passed
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.

3 participants