jambo

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

jambo

Go Reference Go Report Card License GitHub release GitHub issues

JVM Dynamic Attach Utility (Go version)

A Go implementation of JVM dynamic attach mechanism, inspired by jattach.

Features

  • Go API: Use as a library in your Go applications
  • CLI Tool: Command-line interface similar to jattach
  • Cross-platform:
    • ✅ Linux (full support with namespace/container support)
    • ✅ Windows (HotSpot support via remote thread injection)
    • ⚠️ Other platforms (basic stub implementation)
  • JVM Support:
    • ✅ HotSpot JVM (Linux & Windows)
    • ✅ OpenJ9 JVM (Linux only)
  • Container Support: Linux container namespace support (net, ipc, mnt, pid)
  • Comprehensive Documentation: See Documentation section for technical details

Compatibility

HotSpot JVM
JDK Version Status Notes
JDK 6+ ✅ Supported Initial attach protocol
JDK 8 ✅ Supported Enhanced agent loading
JDK 9+ ✅ Supported Enhanced load command response
JDK 11+ ✅ Supported LTS release, fully tested
JDK 17+ ✅ Supported LTS release, fully tested
JDK 21+ ✅ Supported LTS release, load error reporting changes

Minimum Version: JDK 6
Recommended: JDK 8 or later

OpenJ9 JVM
JDK Version Status Notes
JDK 8+ ✅ Supported Requires -Dcom.ibm.tools.attach.enable=yes
JDK 11+ ✅ Supported LTS release, fully tested
JDK 17+ ✅ Supported LTS release
JDK 21+ ✅ Supported LTS release, fully tested

Minimum Version: JDK 8 (with OpenJ9 VM)
Recommended: JDK 11 or later
Required JVM Option: -Dcom.ibm.tools.attach.enable=yes

Note: OpenJ9 attach mechanism differs from HotSpot - it uses TCP sockets and semaphores instead of Unix domain sockets. See docs/OPENJ9.md for details.

Installation

As a library
go get github.com/cosmorse/jambo
Build from source
git clone https://github.com/cosmorse/jambo
cd jambo
go build ./cmd/jambo

Usage

Command Line
jambo <pid> <cmd> [args ...]
Available Commands
  • load : load agent library
  • properties : print system properties
  • agentProperties : print agent properties
  • datadump : show heap and thread summary
  • threaddump : dump all stack traces (like jstack)
  • dumpheap : dump heap (like jmap)
  • inspectheap : heap histogram (like jmap -histo)
  • setflag : modify manageable VM flag
  • printflag : print VM flag
  • jcmd : execute jcmd command
Examples
Load Java agent
jambo <pid> load instrument false "javaagent.jar=arguments"
List available jcmd commands
jambo <pid> jcmd help -all
Take thread dump
jambo <pid> threaddump

Go API

Basic Usage
package main

import (
    "fmt"
    "github.com/cosmorse/jambo"
)

func main() {
    pid := 12345
    
    // Simple attach
    output, err := jambo.Attach(pid, "threaddump", nil, true)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Println(output)
    
    // Advanced usage with options
    proc, err := jambo.NewProcess(pid)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    
    opts := &jambo.Options{
        PrintOutput: true,
        Timeout:     5000,
    }
    
    output, err = proc.Attach("properties", nil, opts)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Println(output)
}
API Reference
Types
// Process represents a target JVM process
type Process struct {
    // Private fields, accessed via methods:
    // Pid() int      - Process ID
    // Uid() int      - User ID
    // Gid() int      - Group ID
    // NsPid() int    - Namespace PID (for containers)
    // JVM() JVM      - JVM implementation instance
}

// Options configures attach operation behavior
type Options struct {
    PrintOutput bool  // Print command output to stdout
    Timeout     int   // Timeout in milliseconds (0 = no timeout)
}

// JVMType represents the JVM implementation type
type JVMType int
const (
    HotSpot JVMType = iota  // Oracle HotSpot or OpenJDK
    OpenJ9                   // Eclipse OpenJ9 (formerly IBM J9)
    Unknown                  // Unknown JVM type
)
Functions
// NewProcess creates a new Process for the given PID with auto-detection of JVM type
func NewProcess(pid int) (*Process, error)

// Attach is a convenience function for quick attach operations
func Attach(pid int, command string, args []string, printOutput bool) (string, error)

// ParsePID parses a PID string (decimal or hex with 0x prefix)
func ParsePID(pidStr string) (int, error)
Methods
// Process methods
func (p *Process) Attach(command string, args []string, opts *Options) (string, error)
func (p *Process) Pid() int
func (p *Process) Uid() int
func (p *Process) Gid() int
func (p *Process) NsPid() int
func (p *Process) JVM() JVM

Documentation

For detailed technical documentation, see:

Platform Support

Linux
  • Full support for HotSpot and OpenJ9 JVMs
  • Container namespace support (net, ipc, mnt, pid)
  • Process credential switching
  • Uses Unix domain sockets for HotSpot
  • Uses TCP/IP sockets with semaphore notification for OpenJ9
Windows
  • HotSpot JVM support via remote thread injection
  • Uses Named Pipes for communication
  • Requires Administrator privileges or SeDebugPrivilege
  • See Windows Shellcode documentation for technical details
Other Platforms
  • Basic API available
  • Limited functionality due to platform constraints

Limitations

  • Namespace switching: Requires CAP_SYS_ADMIN capability on Linux
  • Container support: Works in most container environments (Docker, Kubernetes, etc.)
  • Windows:
    • Requires Administrator privileges or SeDebugPrivilege
    • Bitness must match (32-bit jambo for 32-bit JVM, 64-bit for 64-bit)
    • May be flagged by antivirus software due to remote thread injection
  • OpenJ9 on Windows: Not supported (OpenJ9 attach mechanism differs significantly)

Building

# Build the CLI tool
go build -o jambo ./cmd/jambo

# Build for specific platform
GOOS=linux GOARCH=amd64 go build -o jambo-linux-amd64 ./cmd/jambo
GOOS=windows GOARCH=amd64 go build -o jambo-windows-amd64.exe ./cmd/jambo

Platform-Specific Notes

Linux
  • Full support for HotSpot and OpenJ9 JVMs
  • Container namespace support (net, ipc, mnt, pid)
  • Requires appropriate permissions to attach to processes
  • Uses Unix domain sockets for communication
Windows
  • HotSpot JVM support via remote thread injection
  • Requires Administrator privileges or SeDebugPrivilege to attach to other processes
  • Uses Named Pipes for communication
  • Bitness must match (32-bit jambo for 32-bit JVM, 64-bit for 64-bit)
  • Note: Remote thread injection technique may be flagged by antivirus software

Windows Error Codes:

  • 1001: Could not load JVM module (jvm.dll not found)
  • 1002: Could not find JVM_EnqueueOperation function
Environment Variables
  • JAMBO_ATTACH_PATH: Override default temp path for attach files

Testing

Unit Tests
# Run tests
go test ./...

# Test with coverage
go test -cover ./...
Docker Integration Tests

We provide comprehensive test suites for different JVM types using Docker containers:

cd tests

# Test HotSpot only (recommended)
./run_tests.sh

# Test both HotSpot and OpenJ9
./run_all_tests.sh
Test Environment Status
JVM Type Status Platform Test Coverage Production Ready
HotSpot Working Linux (Docker) 10/10 tests passed YES
OpenJ9 Working Linux 10/10 tests passed YES
Windows ❓ Untested Windows Code review only Needs Testing

License

Apache License 2.0

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Technical References

Attach Mechanism Documentation
Upstream Projects
API Documentation

Documentation

Overview

Package jambo provides a Go implementation of JVM Dynamic Attach mechanism.

This package allows you to dynamically attach to running JVM processes and execute various commands such as loading agents, dumping threads, inspecting heap, and more. It supports both HotSpot and OpenJ9 JVMs on Linux and Windows platforms.

Basic Usage

Simple attach example:

output, err := jambo.Attach(12345, "threaddump", nil, true)
if err != nil {
    log.Fatal(err)
}
fmt.Println(output)

Advanced Usage

Using Process for more control:

proc, err := jambo.NewProcess(12345)
if err != nil {
    log.Fatal(err)
}

opts := &jambo.Options{
    PrintOutput: true,
    Timeout:     5000,
}

output, err := proc.Attach("jcmd", []string{"VM.version"}, opts)
if err != nil {
    log.Fatal(err)
}

Supported Commands

  • load: Load a Java agent into the JVM
  • properties: Print system properties
  • agentProperties: Print agent properties
  • threaddump: Dump all thread stack traces
  • dumpheap: Dump heap to file
  • inspectheap: Print heap histogram
  • datadump: Show heap and thread summary
  • jcmd: Execute arbitrary jcmd command

Platform Support

  • Linux: Full support (HotSpot + OpenJ9, with container namespace support)
  • Windows: HotSpot support via remote thread injection
  • Other platforms: Basic stub implementation

Linux-specific implementation of JVM attach mechanism. This file contains implementations for both HotSpot and OpenJ9 JVMs on Linux.

Example (AdvancedAttach)

Example_advancedAttach demonstrates using Process for more control.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	// Create a Process instance
	proc, err := jambo.NewProcess(12345)
	if err != nil {
		log.Fatal(err)
	}

	// Get JVM type information
	fmt.Printf("JVM Type: %v\n", proc.JVM().Type())
	fmt.Printf("Process PID: %d\n", proc.Pid())
	fmt.Printf("Namespace PID: %d\n", proc.NsPid())

	// Configure attach options
	opts := &jambo.Options{
		PrintOutput: true,
		Timeout:     5000, // 5 seconds
	}

	// Execute jcmd command
	output, err := proc.Attach("jcmd", []string{"VM.version"}, opts)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(output)
}
Example (ContainerSupport)

Example_containerSupport demonstrates attaching to JVMs in containers.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	// When attaching to a containerized JVM, jambo automatically
	// handles namespace switching and uses the correct namespace PID

	proc, err := jambo.NewProcess(12345) // Host PID
	if err != nil {
		log.Fatal(err)
	}

	// Check if process is in a container
	if proc.NsPid() != proc.Pid() {
		fmt.Printf("Container detected - Host PID: %d, NS PID: %d\n",
			proc.Pid(), proc.NsPid())
	}

	// Attach works the same way for containerized processes
	output, err := proc.Attach("threaddump", nil, nil)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(output)
}
Example (ErrorHandling)

Example_errorHandling demonstrates proper error handling.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	// Parse PID from string
	parsedPID, err := jambo.ParsePID("12345")
	if err != nil {
		log.Fatal("Invalid PID:", err)
	}

	// Create process
	proc, err := jambo.NewProcess(parsedPID)
	if err != nil {
		switch err {
		case jambo.ErrInvalidPID:
			log.Fatal("Invalid process ID")
		case jambo.ErrProcessNotFound:
			log.Fatal("Process not found or not accessible")
		default:
			log.Fatal("Error:", err)
		}
	}

	// Execute command
	output, err := proc.Attach("threaddump", nil, nil)
	if err != nil {
		switch {
		case err == jambo.ErrPermission:
			log.Fatal("Permission denied - try running with sudo")
		case err == jambo.ErrCommandFailed:
			log.Fatal("Command execution failed in JVM")
		default:
			log.Fatal("Error:", err)
		}
	}

	fmt.Println(output)
}
Example (GetProperties)

Example_getProperties demonstrates getting system properties.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	proc, err := jambo.NewProcess(12345)
	if err != nil {
		log.Fatal(err)
	}

	// Get all system properties
	output, err := proc.Attach("properties", nil, &jambo.Options{
		PrintOutput: false, // Don't print to stdout
	})

	if err != nil {
		log.Fatal(err)
	}

	// Parse and use properties as needed
	fmt.Println(output)
}
Example (InspectHeap)

Example_inspectHeap demonstrates heap inspection.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	proc, err := jambo.NewProcess(12345)
	if err != nil {
		log.Fatal(err)
	}

	// Get heap histogram
	output, err := proc.Attach("inspectheap", nil, &jambo.Options{
		PrintOutput: true,
	})

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(output)
}
Example (Jcmd)

Example_jcmd demonstrates executing various jcmd commands.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	proc, err := jambo.NewProcess(12345)
	if err != nil {
		log.Fatal(err)
	}

	// Example 1: Get VM version
	output, _ := proc.Attach("jcmd", []string{"VM.version"}, nil)
	fmt.Println("VM Version:", output)

	// Example 2: Get VM flags
	output, _ = proc.Attach("jcmd", []string{"VM.flags"}, nil)
	fmt.Println("VM Flags:", output)

	// Example 3: Run GC
	output, _ = proc.Attach("jcmd", []string{"GC.run"}, nil)
	fmt.Println("GC Result:", output)

	// Example 4: Get thread info
	output, _ = proc.Attach("jcmd", []string{"Thread.print"}, nil)
	fmt.Println(output)
}
Example (LoadAgent)

Example_loadAgent demonstrates how to load a Java agent.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	proc, err := jambo.NewProcess(12345)
	if err != nil {
		log.Fatal(err)
	}

	// Load agent with absolute path
	// Args: [agentPath, isAbsolutePath, options]
	output, err := proc.Attach("load", []string{
		"/path/to/agent.jar",
		"true",                    // absolute path
		"key1=value1,key2=value2", // agent options
	}, nil)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Agent loaded:", output)
}
Example (MultipleCommands)

Example_multipleCommands demonstrates executing multiple commands.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	proc, err := jambo.NewProcess(12345)
	if err != nil {
		log.Fatal(err)
	}

	commands := []struct {
		name string
		cmd  string
		args []string
	}{
		{"Thread Dump", "threaddump", nil},
		{"Heap Summary", "jcmd", []string{"GC.heap_info"}},
		{"VM Info", "jcmd", []string{"VM.info"}},
	}

	for _, c := range commands {
		fmt.Printf("\\n=== %s ===\\n", c.name)
		output, err := proc.Attach(c.cmd, c.args, &jambo.Options{
			PrintOutput: false,
		})
		if err != nil {
			log.Printf("Warning: %s failed: %v", c.name, err)
			continue
		}
		fmt.Println(output)
	}
}
Example (SimpleAttach)

Example_simpleAttach demonstrates the simplest way to attach to a JVM process.

package main

import (
	"fmt"
	"log"

	"github.com/cosmorse/jambo"
)

func main() {
	// Attach to JVM process 12345 and get a thread dump
	output, err := jambo.Attach(12345, "threaddump", nil, true)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(output)
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrProcessNotFound indicates the target process does not exist or cannot be accessed.
	ErrProcessNotFound = errors.New("process not found")

	// ErrInvalidPID indicates the provided process ID is invalid (zero or negative).
	ErrInvalidPID = errors.New("invalid process ID")

	// ErrPermission indicates insufficient permissions to attach to the target process.
	ErrPermission = errors.New("permission denied")

	// ErrCommandFailed indicates the attach command execution failed in the target JVM.
	ErrCommandFailed = errors.New("command execution failed")
)

Functions

func Attach

func Attach(pid int, command string, args []string, printOutput bool) (string, error)

Attach is a convenience function that creates a Process and performs an attach operation. This is the simplest way to attach to a JVM process.

Parameters:

  • pid: Target process ID
  • command: Attach command to execute
  • args: Command arguments (can be nil)
  • printOutput: Whether to print output to stdout

Example:

output, err := jambo.Attach(12345, "threaddump", nil, true)
if err != nil {
    log.Fatal(err)
}
fmt.Println(output)

For more control, use NewProcess() and call Attach() on the Process instance.

func ParsePID

func ParsePID(pidStr string) (int, error)

ParsePID parses a string into a valid process ID. Returns ErrInvalidPID if the string is not a valid positive integer.

Example:

pid, err := jambo.ParsePID(os.Args[1])
if err != nil {
    log.Fatal("Invalid PID:", err)
}

Types

type JVM

type JVM interface {
	// Attach performs the actual attach operation to the target JVM process.
	// It sends the command with arguments and returns the output.
	//
	// Parameters:
	//   - pid: The process ID of the target JVM
	//   - nspid: The namespace PID (for container support, same as pid if not in container)
	//   - args: Command and its arguments
	//   - printOutput: Whether to print output to stdout
	//   - tmpPath: Temporary directory path for attach files
	//
	// Returns:
	//   - string: Command output from the JVM
	//   - error: Any error that occurred during attach
	Attach(pid, nspid int, args []string, printOutput bool, tmpPath string) (string, error)

	// Detect checks if the target process is running this JVM type.
	// Returns true if this JVM implementation is detected.
	Detect(nspid int) bool

	// Type returns the JVM implementation type.
	Type() JVMType
}

JVM defines the interface for JVM attach operations. Different JVM implementations (HotSpot, OpenJ9) provide their own implementations.

type JVMType

type JVMType int

JVMType represents the type of JVM implementation.

const (
	// HotSpot represents Oracle HotSpot or OpenJDK JVM.
	HotSpot JVMType = iota

	// OpenJ9 represents Eclipse OpenJ9 JVM (formerly IBM J9).
	OpenJ9

	// Unknown represents an unidentified JVM type.
	Unknown
)

type Options

type Options struct {
	// PrintOutput determines whether command output should be printed to stdout.
	PrintOutput bool

	// Timeout specifies the maximum time in milliseconds to wait for command completion.
	// A value of 0 means no timeout (not yet implemented).
	Timeout int
}

Options configures the behavior of attach operations.

type Process

type Process struct {
	// contains filtered or unexported fields
}

Process represents a target JVM process that can be attached to. It encapsulates process information and provides methods for attach operations.

Process instances should be created using NewProcess() to ensure proper initialization.

func NewProcess

func NewProcess(pid int) (*Process, error)

func (*Process) Attach

func (p *Process) Attach(command string, args []string, options *Options) (string, error)

Attach performs an attach operation with the specified command and arguments. This is the main method for executing commands in the target JVM.

The method performs the following steps:

  1. Enters target process namespaces (for container support)
  2. Switches to target process credentials (if needed)
  3. Determines the appropriate temp path
  4. Delegates to the JVM-specific implementation

Parameters:

  • command: The attach command to execute (e.g., "threaddump", "jcmd", "load")
  • args: Additional arguments for the command
  • options: Attach options (nil for defaults)

Example:

proc, _ := jambo.NewProcess(12345)
output, err := proc.Attach("threaddump", nil, &jambo.Options{
    PrintOutput: true,
})
if err != nil {
    log.Fatal(err)
}

Example with jcmd:

output, err := proc.Attach("jcmd", []string{"VM.version"}, nil)

Example loading an agent:

output, err := proc.Attach("load", []string{"/path/to/agent.so", "false", "options"}, nil)

Returns ErrCommandFailed if the command execution fails in the JVM.

func (*Process) Gid

func (proc *Process) Gid() int

Gid returns the group ID of the process owner. This is used for credential switching when attaching.

func (*Process) JVM

func (proc *Process) JVM() JVM

JVM returns the JVM implementation instance for this process. The instance type (HotSpot or OpenJ9) is automatically detected during Process creation.

func (*Process) NsPid

func (proc *Process) NsPid() int

NsPid returns the namespace process ID. In containers, this may differ from Pid(). Otherwise, it's the same as Pid().

func (*Process) Pid

func (proc *Process) Pid() int

Pid returns the process ID of the target JVM process.

func (*Process) Uid

func (proc *Process) Uid() int

Uid returns the user ID of the process owner. This is used for credential switching when attaching.

Directories

Path Synopsis
cmd
jambo command
jambo is a command-line tool for attaching to running JVM processes.
jambo is a command-line tool for attaching to running JVM processes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL