You are here

Planet GNOME

Subscribe to Feed Planet GNOME
Planet GNOME - https://planet.gnome.org/
Përditësimi: 19 orë 28 min më parë

Matthew Garrett: SystemIO conflicts are not firmware bugs

Mër, 09/09/2026 - 8:15md

I’m looking at something entirely unrelated, but tripped over some search results that made me realise that a lot of people still think getting errors like ACPI Warning: SystemIO range 0x0000000000001828-0x000000000000182F conflicts with OpRegion 0x0000000000001800-0x000000000000187F indicate a firmware bug. This is generally untrue. We need to dive a little into what ACPI is to clarify why.

The Advanced Configuration and Power Interface1 specification defines a whole bunch of stuff, but what’s interesting to us here is the hardware abstraction it performs. While PCs are nominally a well-defined platform that’s really not true at the hardware level once you get beyond a certain level of complexity. When you suspend a system you want to power down the hardware in the correct order, for instance, and knowing what that order is requires you to know details about the specific motherboard design. The approach taken in the embedded world is to just bake that knowledge into the OS in some form, which is how we end up with Devicetree. ACPI takes an alternative approach - rather than provide that information as data that has to be consumed by OS drivers, it distributes it as code.

The ACPI Source Language, or ASL, is a simple language that gets compiled into a bytecode that’s then interpreted by the OS at runtime. One of the features of this language is the ability to define “Operation Regions”, effectively structure definitions that describe access to underlying hardware. Let’s imagine a simple device with two exposed registers. The first is an index register - it describes which internal register we want to access. The second is a data register, where reading it gives us the value of the internal register whose address is currently in the index register, and writing to it modifies that register. An example operation region declaration would look something like

1 2 3 4 5 6 OperationRegion(OPR1, SystemIO, 0x400, 0x2) Field(OPR1, ByteAcc, NoLock, Preserve) { INDX, 8 DATA, 8 }

This defines an operation region called “OPR1” at IO port 0x400, 2 bytes long. Inside it are two 8-bit fields, INDX and DATA. These are to be accessed one at a time, do not need the ACPI interpreter to take a global lock when accessing them, and if a subset of the register is modified then the other values should be preserved (irrelevant in this case since the fields are only a byte wide). Now any references to INDX or DATA in this scope will trigger accesses to those registers. So, a method to read the value of register 0x03 would look something like:

1 2 3 4 Method (RD03) { INDX = 0x3 Return (DATA) }

ie, set INDX to 3, and then read the value of DATA and return it. But! What if another ACPI method is running at the same time? Let’s say we have one that writes to register 0x05:

1 2 3 4 Method (WR05, 1) { INDX = 0x05 DATA = Arg1 }

What happens if RD03 executes while we’re part-way through WR05? INDX might get reset to 0x03, and now WR05 will modify register 0x03 instead of 0x05. Oh no! But we can avoid this - we declare a mutex (Mutex (MUTX, 0x00)), and update our methods to be something like:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 Method (RD03) { Acquire (MUTX, 0xFFFF) INDX = 0x3 Local0 = DATA Release (MUTX) Return (Local0) } Method (WR05, 1) { Acquire (MUTX, 0xFFFF) INDX = 0x05 DATA = Arg1 Release (MUTX) }

Each method takes a lock (waiting up to 0xffff milliseconds and then erroring out if it doesn’t), and performs the access. There’s now no chance of a race. Phew!

Now suppose someone writes a Linux driver for this piece of hardware. It accesses the hardware directly, with no knowledge of ACPI. What stops the driver from racing against one of the ACPI access methods? Nothing at all. Oh no! Again! This isn’t hypothetical, by the way - here’s a relatively harmless example, but back in the day we did trip over cases where temperature monitoring chips would be accessed by the firmware and Linux simultaneously and as a result you might end up thinking you’re reading a temperature when you’re actually reading a status flag, resulting in an impossibly high temperature and an immediate thermal shutdown.

In this case, the kernel saves you from this (potentially hardware damaging) outcome by printing a message like ACPI Warning: SystemIO range 0x0000000000000400-0x000000000000401 conflicts with OpRegion 0x0000000000000400-0x0000000000000401 (OPR1), telling you that the kernel has detected that a driver is attempting to allocate IO ports 0x400-0x401, but that there’s an ACPI operation region called OPR1 that is claiming the same addresses. The kernel isn’t in a position to know what type of access the firmware might perform in that region, so assumes that it might be dangerous and blocks the driver from loading.

But all is not lost! The kernel also prints some helpful advice, ACPI: If an ACPI driver is available for this device, you should use it instead of the native driver. And ACPI tables will often actually have a definition that looks like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Device (HDW1) { Name (_HID, "VEND0001") OperationRegion(OPR1, SystemIO, 0x400, 0x2) Field(OPR1, ByteAcc, NoLock, Preserve) { INDX, 8 DATA, 8 } Mutex (MUTX, 0) Method (RD03) { Acquire (MUTX, 0xFFFF) INDX = 0x3 Local0 = DATA Release (MUTX) Return (Local0) } Method (WR05, 1) { Acquire (MUTX, 0xFFFF) INDX = 0x05 DATA = Arg1 Release (MUTX) } }

which defines an ACPI device and associated methods. The _HID field defines the device type, and a Linux driver can be written that will be automatically loaded if a device with type VEND0001 is seen. That driver can then call ACPI methods associated with the device and access the resources in a way that matches the firmware’s expectations.

(Interested in writing such a driver? I wrote a guide back in 2009)

The firmware did absolutely nothing wrong here2, but trying to load the native ddriver will generate an error and the internet will tell you that PC firmware developers are incompetent3 and you should pass a kernel argument that overrides this behaviour and it never did them any harm, and it probably won’t do you any harm either but it might and you might never know why your system occasionally wedges or catches fire.

  1. The ACPI spec used to live at acpi.info, but sadly that seems to have vanished some time after UEFI took over stewardship of the spec ↩︎

  2. You might argue that the firmware should simply not do anything at runtime because it is not the firmware’s job to do that, and I do understand that and you can certainly boot with acpi=off if you want to and no ACPI code will be executed at runtime. Let me know how that goes. ↩︎

  3. I’m not going to present an opinion on that here, merely say that this provides no supporting evidence for that assertion ↩︎

vixalien: Project Final Report: Adding Debug Adapter Protocol Support to GJS

Hën, 07/09/2026 - 2:00pd

Hello again! A few weeks ago, I wrote about the work I've been doing this summer adding Debug Adapter Protocol (DAP) support to GJS as part of Google Summer of Code (GSoC) 2026. If you haven't read that post, start there for the background on what GJS and DAP are and why this matters.

As my GSoC is wrapping up, I wanted to share you an update on what I've done, what I've learnt, and what I'm planning for the future.

Instead of a lengthy report, I actually want to walk you through debugging a real GJS application using the DAP support I've added to GJS.

By the end of this post, you'll know how to launch a GJS app in Zed, set breakpoints (including on exceptions), step through code, inspect variables and more, all from inside your editor.

Setting Up

The code I've implemented is currently in a Merge Request being reviewed, so to you use it, you will need to clone and build GJS from source (until GNOME 52).

Cloning and Building GJS from source

You can build GJS from source by following the Hacking guide, but here's a shorter version of it

# 1. Clone GJS git clone https://gitlab.gnome.org/GNOME/gjs.git cd gjs # 2. Checkout my branch git checkout wip/vixalien/dap # 3. Setup meson meson setup _build # 4. Build GJS ninja -C _build # 5. Verify meson devenv -C _build gjs-console ../script.js

This will be required before GNOME 52.

Please note the path where you cloned GJS (e.g. ~/Projects/gjs). We will need it later.

Editor setup

You will also need to download and install the Zed editor. The currently supported editors for GJS DAP are Zed and VS Code. We will use the Zed editor since it's more validated to work with the GJS DAP support currently.

You will also need to install the GJS Debugger Extension for Zed, which is currently pending review to be included in the Zed extension store.

But you can build it locally, by cloning my Extension. To install within Zed, Press Ctrl+Shift+X, then click "Install Dev Extension". A file picker will open, so navigate to the directory where you cloned the extension and select it.

This will require a Rust toolchain to be installed, so the extension can be built.

Let me know if you want to debug GJS apps from other editors (not just Zed).

Navigating Around

To make this concrete, I'm going to walk through debugging an standard example application.

1. Setting up the application

The application we are going to debug is a simple Calculator, as found in the GJS Examples

Create a simple file called calc.js in a new project directory and save the contents of the Calculator app above into it.

Then open the project in Zed as you normally would.

2. Opening the Project in the Debugger

To open the project in the Debugger, you can use the F4 key to start debugging.

A dialog will then pop up asking for the Debugger configuration.

  1. Select the Launch tab to launch a new debugger instance.
  2. Select GJS as the debugger.
  3. Type calc.js as the program to debug.
  4. Disable "Stop On Entry" so that the debugger doesn't stop at the first line of the script.
  5. Press Ctrl+Enter or select "Edit in debug.json" to open the configuration file.

This will create a new configuration file at .zed/debug.json in the project directory, we will use this file to configure the debugger and make sure our debugger settings are saved across sessions.

That file will look like this:

// Project-local debug tasks // // For more documentation on how to configure debug tasks, // see: https://zed.dev/docs/debugger [ { "adapter": "gjs", "label": "calc.js (gjs)", "args": [], "cwd": "/home/alien/Projects/calc", "program": "calc.js", "stopOnEntry": false, }, ]

We will need to make a small modification to it to point it to the GJS we just compiled (otherwise it will use the default GJS from our system, which doesn't have the unmerged DAP changes).

This is needed before GNOME 52 is released (which means gjs will be able to do this natively).

We will do it by adding a gjsPath field to the configuration in this format:

... "program": "calc.js", "stopOnEntry": false, + "gjsPath": "flatpak-spawn --host meson devenv -C ~/Projects/gjs --workdir . gjs-console", }, ]

Where ~/Projects/gjs is the path to the GJS repository you cloned.

After making this change, press F5 again, and now you will see an option called calc.js (gjs) in the dialog's "Debug" tab.

Click that configuration, and this will launch the debug configuration we just saved.

Now you have a running GJS debugger session!

3. Navigating the Debugger

At the bottom of the window, you will see a debug toolbar with various sections, panes and controls.

Fret not! The debugger toolbar is simple to understand, as I will explain here below.

The debugger toolbar is made up of controls at the top, then 3 horizontal panes.

1. The controls bar

This is where you have different buttons to control the state of the program. In order, we have the Pause/Resume button, Step Over (or Next) button, Step In, Step Out, then the Restart and Quit buttons.

2. The frames pane

This pane shows the currently active stack frames (or call stacks).

This frame has another tab that shows the various set breakpoints.

3. The console pane

This pane shows the console output of the program and allows you to potentially execute commands (not yet supported in the GJS debugger).

It has a different tab that shows the different scopes. Here, you can expand a scope to see variables inside that scope.

4. The terminal pane

Last, but not least, the terminal pane shows regular terminal output from the running program. This is also not currently implemented in the GJS debugger.

Debugging

Now that you can navigate around the debugger, let's get to debugging!

1. Using the debugger statement.

The debugger statement is a built-in statement in JavaScript that pauses execution and allows you to inspect the current state of the program at the time it pauses.

You can add a debugger statement to calc.js at the end of the file to test it out.

Then click F5 again to start debugging. This will launch the debugger and pause execution at the debugger statement.

Note: Ignore the "the debugger statement is not allowed" message for now, but remember to remove it before building/shipping your application.

The highlighted line is where the debugger paused execution.

2. Inspecting Variables

With the debugger now paused, you can inspect the variables in the current scope.

Click on a scope's name to expand the variables under it.

You can click on one of the objects to inspect its properties, for example, in the module scope, click on Gtk to see all the widgets available in the GTK library.

Inspecting all types of variables is implemented and you can inspect numbers, booleans, strings, symbols, functions, classes and most other types of objects.

3. Adding breakpoints

Adding the debugger statement is not the only way you can stop execution, you can also quite easily add breakpoints by clicking on the line number you want to pause at in the editor.

For example, let's add a breakpoint on the first line of the pressedEquals function.

Then we can stop and restart the debugger. In the running program, type a simple equation like 1+1, then click =.

The debugger panel will now show that you're paused, and allow you to view the stack frames as well as the scopes.

With this approach, you can debug applications and pause execution at any point to inspect the state of the program.

Also note that the breakpoints tab is now updated to show the breakpoint we just set.

Note: The main Calculator window might now appear as Frozen (e.g. with a "« gjs-console » is not responding" message). Don't worry, this is because the program is paused in the debugger.

Note2: You can set/remove breakpoints anytime the app is running or before it starts.

4. Stepping through the code

With the application now paused, we can progressively move execution line-by-line by stepping through the code.

To "Step Over" (execute the current line and move to the next one), press the "Step Over" button in the debugger toolbar.

<video src="/images/posts/gjs-dap-report/equals-stepping.webm" loop muted autoplay controls></video>

You can also click the "Step Into" button to step into a function call (or just step over).

Here's an example where I've added a breakpoint on Line 40 (first line of pressedOperator button) and stepping into the updateDisplay function call.

<video src="/images/posts/gjs-dap-report/step-into.webm" loop muted autoplay controls></video>

Stepping back is currently not implemented.

5. Breaking on Exceptions

Another way to pause execution is to set to break on exceptions. The GJS debugger supports breaking on breakpoints that would either be caught (i.e. in a try {} catch {} block) or not caught (i.e. unhandled exceptions).

You can set these options by going to the Breakpoints tab and then clicking either the "Uncaught Exceptions" or "Caught Exceptions" button (or both).

VS Code Extension

I've also worked on a VS Code extension, which enables debugging GJS applications inside of VS Code, however it reamins highly experimental and many features are not working yet.

This is because I focused on the Zed extension and it's the one I used during development extensively, so the VS Code extension is not as well tested as the Zed one, but I am also planning to improve it and submit it to the VS Code extensions marketplace in-time for the GNOME 52 release!

You can find instructions to use the VS Code extension in it's repo. Here is an example of it debugging an application:

<video src="/images/posts/gjs-dap-report/vscode.webm" loop muted autoplay controls></video>

Challenges

While working on this project, I had a few challenges:

Firstly, I really had trouble working well because of the remote nature of GSoC, and sometimes collaborating with my mentor would get off-tracked because I tended towards working alone instead of realising my mentor was available to help me. For future participants, I would advise you to realise that your mentor is available to help you, instead of feeling like you should be 100% independent. In my experience, a mentor will usually point you to the right solution, or even help you understand topics you might otherwise get blocked on for too long.

Code-wise, the most challenging part was getting the message parsing (i.e. sending DAP messages and receiving them through stdio) to work. I tried many approaches on my own (see point 1 above) but at the end it got resolved when I decided to ask my mentor for help.

The issue was complex because we needed to have access to the standard input as a stream so we can parse the protocol's Content-Length: {nBytes}\r\n headers, then read the corresponding number of bytes exactly. My first instinct was to use Gio.DataInputStream directly, but it didn't because it wasn't possible to load Gio/GLib imports in the main realm. The solution was to create a few functions (openInputStream, readLine and readBytes) on the C++ side since it can use the Gio/GLib APIs, then expose them to the JS code that implements the DAP communication (and linking with Firefox/Spidermonkey's Debugger API).

Another challenge I had was when implementing the VS Code extension. In the beginning, I wrote a Zed extension that would expose GJS' DAP capabilities to the Zed Editor. When working on a similar extension for VS Code, I got stuck a bit because VS Code doesn't have a native way to easily show the communications happening between the DAP client (in this case VS Code) and the DAP server (GJS), while Zed had an easy way to show them. This effectively hid a bug where Zed was sending/requesting an extra /r/n in the DAP requests & responses, while VS Code was not (they both implemented the standard differently). In the end, I created a wrapper script that would also log all the communications between the client and the server differently so I can diagnose that bug and fix it.

A recommendation I would give to future GSoC participants is to also track time and progress well. When working on the project, I didn't regularly check my proposal and the different activities and their timelines, so I ended up moving/reprioritising tasks towards the end of the program, which could have been avoided if I always checked the timeline to make sure I'm still on track and adjusting early.

Further Steps

There are some remaining tasks that could be done to make the GJS debugger better, and here's some of them.

  1. Bring the VS Code extension to feature parity as the Zed extension (see above).
  2. Add support for debugging GJS applications in GNOME Builder: Currently blocked by GNOME Builder itself lacking DAP support
  3. Add support for evaluating expressions in the debugger when paused.
  4. Correctly stop/kill the script when the debug session ends.
  5. Enabling source map support, which will make debugging compiled GJS (and TypeScript!) applications (like GNOME Weather, GNOME Sound Recorder) easier.
  6. Testing and ensuring the debugger works well on macOS and Windows (I only tested on Linux).
  7. Redirect console.log and other output to the debug console.
  8. Allow attaching to already running GJS applications (potentially by implementing a SIGUSR1 handler and communicating via unix socket).
  9. Allow pausing the program that's being debugged (at any point).
  10. Implement setting or modifying variables in the debugger.
  11. Give information about the current exception when we hit an exception breakpoint (needs the VS Code extension).
  12. Maybe implement watching source code and live-reload of the code while debugging.
  13. Implement more DAP capabilities (e.g. function breakpoints, conditional breakpoints) to improve the debugging experience even more (including correct presentationHint)
  14. Show the scopes in a better way (e.g. merge the global and GjsGlobal scopes, potentially merge the class body scopes, etc...)
  15. Maybe support debugging the GNOME Shell??
  16. Maybe implement GJS debugging (and provide instructions) for other DAP clients like Emacs, Vim, etc. (see full list of tools implementing DAP here)
  17. Maybe add documentation for debugging a GJS application while developing with meson (will need to add a run_target).

Let me know if there's more support you may want, or if you'd like to work on any of these.

Improving WASM Support

As part of the GSoC project, during the initial community bonding period, I also worked on improving WASM support in GJS. The MR essentially connects WASM's event loop to the GLib main loop set up by GJS.

Conclusion

I would like to thank Google Summer of Code for selecting me to work on this project, which I hope will improve the experience of writing, debugging and improve GJS applications.

I'd also like to thank the GNOME Project for hosting GJS, which is an important part of the GNOME ecosystem.

Finally, I'd like to thank my mentor Philip Chimento so much for his important skills, guidance, and support while I was working on this project.

You can reach out in the GNOME JavaScript room in Matrix: #javascript:gnome.org for any questions or feedback.