Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

LuaJIT support, designed to support versions of openresty from 1.15+ #14

Merged
merged 2 commits into from
Nov 12, 2024

Conversation

gnurizen
Copy link
Collaborator

@gnurizen gnurizen commented Oct 9, 2024

Supports stripped versions of LuaJIT which is common and supports amd64
and arm64. LuaJIT compressed 32 bit pointer mode is not supported.

Offsets are pulled from native code analysis using Go's builtin
x86asm and arm64asm packages. Zydis wasn't needed as the offsets are
mostly extracted from from the beginning of functions so none of x86asm
limitations were hit. Docker is used to extensively test the offset
extraction and do some simple integration testing (sudo required).

Assumptions are baked into the Go code and to a minimal extent the
unwinder code about the layout of LuaJIT structs, the codebase is old
and stable so making things fully generic was not necessary.

The unwinder supports interpreted and JIT'd execution, the fidelity of
JIT'd profiles is somewhat limited as the bytecode "PC" is not tracked
in JIT execution. However the call stack is maintained so even though
a JIT'd LuaJIT trace can wind through any number of functions a full
stack trace can be constructed.

Tested on openresty docker images from 1.15 through 1.25 (latest).

Most of the changes are in line with other native unwinders with a
couple exceptions:

  1. The LoaderInfo is now given access to the stack deltas. This allows
    the stack deltas to be consulted to determine the bounds of the
    LuaJIT interpreter "function" (see comments for
    extractInterpreterBounds).

  2. The way the JIT traces are mapped is a little odd. First we establish
    the entire anonyous/executable section with a pid mapping and then
    as we learn about and walk the GCtrace objects we fill in specific
    information about how to walk over each JIT trace. The stack adjustments
    are specific to each trace and we store the LuaJIT global G pointer
    in the pid mapping table to save the unwinder from having to remember
    context and rely on best effort techniques to find it. This should
    have the advantage of making the unwinder work in untested contexts like
    multi-threaded openresty or applications where multiple LuaJIT instances
    are used fom the same process/threads.

@gnurizen gnurizen requested review from brancz and umanwizard October 9, 2024 22:48
@gnurizen gnurizen marked this pull request as draft October 9, 2024 22:53
@gnurizen gnurizen force-pushed the lua branch 4 times, most recently from 6e6a16e to 770959f Compare October 10, 2024 14:25
@gnurizen
Copy link
Collaborator Author

Curious what others think about the dependency explosion from testcontainers. I'm thinking we could switch the integration tests to otel corefile machinery and use command line docker for the offset testing purposes and get rid of it. I'll explore those options.

@gnurizen gnurizen force-pushed the lua branch 2 times, most recently from 0c0701b to 6b01ad8 Compare October 24, 2024 13:07
Copy link
Collaborator

@umanwizard umanwizard left a comment

Choose a reason for hiding this comment

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

I'm most of the way through with this, not all the way, but leaving my current batch of comments now.

They are mostly just nits, questions, or requests for further comments/docs. The overall approach seems great to me! I think we're very close to being able to finally land this.

Copy link
Collaborator

@umanwizard umanwizard left a comment

Choose a reason for hiding this comment

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

LGTM! I think we're ready to merge this once I update the fork (should be today)

Supports stripped versions of LuaJIT which is common and supports amd64
and arm64.  LuaJIT compressed 32 bit pointer mode is not supported.

Offsets are pulled from native code analysis using Go's builtin
x86asm and arm64asm packages. Zydis wasn't needed as the offsets are
mostly extracted from from the beginning of functions so none of x86asm
limitations were hit. Docker is used to extensively test the offset
extraction and do some simple integration testing (sudo required).

Assumptions are baked into the Go code and to a minimal extent the
unwinder code about the layout of LuaJIT structs, the codebase is old
and stable so making things fully generic was not necessary.

The unwinder supports interpreted and JIT'd execution, the fidelity of
JIT'd profiles is somewhat limited as the bytecode "PC" is not tracked
in JIT execution.  However the call stack is maintained so even though
a JIT'd LuaJIT trace can wind through any number of functions a full
stack trace can be constructed.

Tested on openresty docker images from 1.15 through 1.25 (latest).

Most of the changes are in line with other native unwinders with a
couple exceptions:

1) The LoaderInfo is now given access to the stack deltas.  This allows
the stack deltas to be consulted to determine the bounds of the
LuaJIT interpreter "function" (see comments for
extractInterpreterBounds).

2) The way the JIT traces are mapped is a little odd. First we establish
the entire anonyous/executable section with a pid mapping and then
as we learn about and walk the GCtrace objects we fill in specific
information about how to walk over each JIT trace. The stack adjustments
are specific to each trace and we store the LuaJIT global G pointer
in the pid mapping table to save the unwinder from having to remember
context and rely on best effort techniques to find it.  This should
have the advantage of making the unwinder work in untested contexts like
multi-threaded openresty or applications where multiple LuaJIT instances
are used fom the same process/threads.

update tracers

Remove ReportFallbackSymbol

Fix synchronize mappings for realloc case and add tests

remove unused code

Revert FileIDMapper changes

remove lj_bc headers and manually code in go

Store all information needed to symbolize a frame in one frame instead of using previousFrame hack

cleanup

test cleanup
@gnurizen gnurizen force-pushed the lua branch 2 times, most recently from 3732626 to 1037c96 Compare November 12, 2024 00:49
@gnurizen gnurizen marked this pull request as ready for review November 12, 2024 00:51
@gnurizen gnurizen merged commit 1bcbda5 into main Nov 12, 2024
22 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.

2 participants