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

Crash Analyzer Agent #814

Open
wants to merge 140 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
34b8d65
fix typo
maoyixie Nov 7, 2024
4eb99d2
crash analyzer agent prototype
maoyixie Nov 7, 2024
3d75099
add _execute_agent_cloud in crash analyzer
maoyixie Nov 7, 2024
eb556a7
save cloud testcase
maoyixie Nov 7, 2024
c11a2ae
support gpt chat model
maoyixie Nov 7, 2024
ef90d46
update lldb _handle_conclusion
maoyixie Nov 7, 2024
55fcbed
update prompt, compile project
maoyixie Nov 7, 2024
50d379e
update CrashResult
maoyixie Nov 7, 2024
98e216c
temp, revert later
maoyixie Nov 7, 2024
f7096bd
try to fix gpt model
maoyixie Nov 7, 2024
c10bd27
try to fix gpt model
maoyixie Nov 7, 2024
705aa06
todo: fix openai prompt
maoyixie Nov 7, 2024
6590fda
add tutorial with add_problem instead of append
maoyixie Nov 7, 2024
8177330
try to fix invalid type for message in gpt(todo)
maoyixie Nov 7, 2024
4d8e354
matain gpt chat session, ugly fix invalid message
maoyixie Nov 7, 2024
da6d96e
update
maoyixie Nov 7, 2024
3e59be8
fix agent name
maoyixie Nov 7, 2024
8e8f5e8
fix crash analyzer no benchmark
maoyixie Nov 7, 2024
b589684
reformat
maoyixie Nov 7, 2024
d10587b
delete unused import
maoyixie Nov 7, 2024
31ac644
fix line to long
maoyixie Nov 7, 2024
9a38d93
fix line too long
maoyixie Nov 7, 2024
ab55a08
fix warning
maoyixie Nov 7, 2024
a618b4d
fix warning
maoyixie Nov 7, 2024
e1985c1
crash result inherit from run result
maoyixie Nov 7, 2024
4087145
reformat
maoyixie Nov 7, 2024
54a0579
pass run_result instead of result_history to crash analyzer execute()
maoyixie Nov 7, 2024
1c50caf
revert to match abstract method in Resykt
maoyixie Nov 7, 2024
c0ed437
add type check
maoyixie Nov 7, 2024
732fef3
fix "Prompt" must return value on all code paths
maoyixie Nov 7, 2024
6f9dd49
fix Argument missing for parameter "crash_func"
maoyixie Nov 7, 2024
5d8562e
fix Argument missing for parameter "crash_func"
maoyixie Nov 7, 2024
179d34c
fix Dangerous default value {} as argument
maoyixie Nov 7, 2024
7d87af5
fix crash_func
maoyixie Nov 7, 2024
6d87813
fix Argument missing for parameter "crash_func"
maoyixie Nov 7, 2024
fb6b4f3
fix RunResult in execute in crash analyzer
maoyixie Nov 7, 2024
be85aeb
fix some errors
maoyixie Nov 7, 2024
f7bde92
fix some errors
maoyixie Nov 7, 2024
9140b60
fix Result
maoyixie Nov 7, 2024
ea37525
fix Result
maoyixie Nov 7, 2024
bacea64
fix Function with declared return type "CrashResult" must return valu…
maoyixie Nov 7, 2024
7fefe34
"benchmark" is unbound
maoyixie Nov 7, 2024
1b2033d
delete output.txt
maoyixie Nov 7, 2024
9568774
fix type
maoyixie Nov 7, 2024
4190fc5
try to fix unterminated s command, more logger info
maoyixie Nov 7, 2024
526aeda
try to fix compile failure
maoyixie Nov 7, 2024
bb42587
try to fix -g: not a valid identifier
maoyixie Nov 7, 2024
c8842bd
try to fix command in dockerfile
maoyixie Nov 7, 2024
e0f0fb3
try to fix command in dockerfile
maoyixie Nov 7, 2024
e891bea
TODO: some issues like missing artifact and lldb command failure, del…
maoyixie Nov 7, 2024
24eb2dc
print more info, format
maoyixie Nov 7, 2024
3aed1c9
try to fix Anomalous backslash in string
maoyixie Nov 7, 2024
af7e1c7
try to fix did not copy fuzz target and build script
maoyixie Nov 7, 2024
c8a8901
Delete output1.txt
maoyixie Nov 7, 2024
b9288ec
Delete output2.txt
maoyixie Nov 7, 2024
ab73185
adjust cycle times
maoyixie Nov 7, 2024
93fda5c
delete comment
maoyixie Nov 7, 2024
a10ae07
reformat
maoyixie Nov 7, 2024
7ce2e71
line break
maoyixie Nov 7, 2024
e19eeb0
optimize artifact name
maoyixie Nov 7, 2024
4908f5e
try to fix failed to match conclusion
maoyixie Nov 7, 2024
93393b9
TODO: fix TypeError: Object of type set is not JSON serializable
maoyixie Nov 7, 2024
d5c5f1f
optimize lldb prompt, try to fix lldb inconsistency
maoyixie Nov 7, 2024
299b316
delete some info
maoyixie Nov 7, 2024
a17f593
reformat
maoyixie Nov 7, 2024
f0882a3
optimize lldb prompt, try to fix ERROR: The required directory < does…
maoyixie Nov 7, 2024
f9d5b14
try to fix lldb
maoyixie Nov 7, 2024
93561cd
fix -e
maoyixie Nov 7, 2024
6c64fb1
try to fix
maoyixie Nov 7, 2024
11addf8
optimize lldb prompt, ignore insignificant errors
maoyixie Nov 7, 2024
2a117fa
optimize lldb prompt
maoyixie Nov 7, 2024
696494b
optimize lldb prompt
maoyixie Nov 7, 2024
e422241
delete info
maoyixie Nov 7, 2024
d838f68
fix fuzzing_language
maoyixie Nov 20, 2024
9f43d4b
modify comment
maoyixie Nov 23, 2024
bd7b572
Create temp
DonggeLiu Nov 7, 2024
9a947b4
delete temp
maoyixie Feb 25, 2025
7ac49be
fix import
maoyixie Feb 25, 2025
071a000
fix typo
maoyixie Feb 25, 2025
5706e69
fix termination condition
maoyixie Mar 1, 2025
f84e821
analysis stage
maoyixie Mar 1, 2025
e37a979
work dir
maoyixie Mar 1, 2025
8c7ee9f
cloud builder
maoyixie Mar 1, 2025
1fc462c
lldb
maoyixie Mar 1, 2025
aa97a65
fix crash analyzer
maoyixie Mar 1, 2025
3b376aa
execution stage
maoyixie Mar 1, 2025
06bc63a
results
maoyixie Mar 1, 2025
077730c
add info in prototyper
maoyixie Mar 1, 2025
4dc82a8
fix lint
maoyixie Mar 1, 2025
a0eb551
fix RunResult
maoyixie Mar 1, 2025
1dc4f16
fix RunResult
maoyixie Mar 1, 2025
6e76339
fix lint, fix prompt
maoyixie Mar 2, 2025
b0f20e2
delete info
maoyixie Mar 2, 2025
3cf6555
fix typo
maoyixie Mar 4, 2025
fda7118
CFLAGS
maoyixie Mar 6, 2025
806e935
delete language condition
maoyixie Mar 6, 2025
c96f116
delete JCC
maoyixie Mar 6, 2025
b19b13f
rename image name
maoyixie Mar 6, 2025
cc5f8d4
recover build_result
maoyixie Mar 6, 2025
a7d7f24
terminate when it is not AnalysisResult
maoyixie Mar 6, 2025
8c7849a
delete blank line
maoyixie Mar 6, 2025
f2793f9
assert RunResult
maoyixie Mar 6, 2025
347ca5a
recover comment
maoyixie Mar 6, 2025
98be2ce
assert RunResult
maoyixie Mar 6, 2025
8795862
inherit ProjectContainerTool
maoyixie Mar 6, 2025
0a83974
detach SemanticCheckResult from RunResult
maoyixie Mar 6, 2025
e6b6881
OpenAIPrompt append
maoyixie Mar 7, 2025
d05a962
lldbtool inherit from containertool
maoyixie Mar 7, 2025
3486a8e
use trial ID to distinguish artifact folder
maoyixie Mar 7, 2025
fc248b8
not overwrite _container_handle_bash_command
maoyixie Mar 8, 2025
d244370
rename _handle_conclusion
maoyixie Mar 8, 2025
847c683
optimize CrashResult initialization
maoyixie Mar 8, 2025
aceae60
use original artifact name
maoyixie Mar 8, 2025
71ddb82
detach _copy_crash_file()
maoyixie Mar 8, 2025
879b460
add trial ID to log
maoyixie Mar 10, 2025
2006aee
optimimze LLDBTool inherit from ProjectContainerTool
maoyixie Mar 10, 2025
1a088ae
optimize model class with chat session
maoyixie Mar 10, 2025
84167ab
optimize handle_lldb_command
maoyixie Mar 12, 2025
d52b48b
reuse create_ossfuzz_project
maoyixie Mar 12, 2025
792c1f3
deduplicate chat_llm
maoyixie Mar 13, 2025
5f5ae1b
fix lint
maoyixie Mar 13, 2025
0041af1
change create_ossfuzz_project into staticmethod, fix lint
maoyixie Mar 13, 2025
bb120c9
fix lint
maoyixie Mar 13, 2025
ca8f522
fix loss t
maoyixie Mar 13, 2025
0ebad67
Merge branch 'main' into dev9
DonggeLiu Mar 14, 2025
edd3a74
Fix 'No such file or directory' error
DonggeLiu Mar 14, 2025
7e6a99d
Fix 'NameError: name 'WorkDirs' is not defined'
DonggeLiu Mar 14, 2025
2ffc79f
Merge branch 'main' into dev9
maoyixie Mar 14, 2025
a4f30a4
fix lint
maoyixie Mar 14, 2025
406a1aa
Write fuzz target and build script to the expected locations in the n…
DonggeLiu Mar 14, 2025
0f0d5d6
Fix artifact blobing
DonggeLiu Mar 20, 2025
cf3b091
Merge branch 'main' into dev9
DonggeLiu Mar 20, 2025
1671da2
fix author
DonggeLiu Mar 20, 2025
af2ccfc
Fix blobbing
DonggeLiu Mar 20, 2025
6fafd6c
Various bug fixes
DonggeLiu Mar 21, 2025
4d21afc
Fix initialization
DonggeLiu Mar 21, 2025
e8df9e2
code move to _copy_cloud_artifact
maoyixie Mar 24, 2025
ec06f99
add bash tool for crash analyzer, add crash analyzer prompt
maoyixie Mar 29, 2025
87ac0fc
fix lint
maoyixie Mar 29, 2025
d489b40
fix wrap lldb in bash, use screen to maintain lldb session
maoyixie Mar 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 192 additions & 1 deletion agent/crash_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,199 @@
"""An LLM agent to analyze and provide insight of a fuzz target's runtime crash.
Use it as a usual module locally, or as script in cloud builds.
"""
import os
import shutil
import subprocess as sp
from typing import Optional

import logger
from agent.base_agent import BaseAgent
from experiment import evaluator as evaluator_lib
from experiment.workdir import WorkDirs
from llm_toolkit import prompt_builder
from llm_toolkit.prompts import Prompt
from results import CrashResult, Result, RunResult
from tool.container_tool import ProjectContainerTool
from tool.lldb_tool import LLDBTool

MAX_ROUND = 100


class CrashAnalyzer(BaseAgent):
pass
"""The Agent to analyze a runtime crash and provide insight to fuzz target."""

def _initial_prompt(self, results: list[Result]) -> Prompt:
"""Constructs initial prompt of the agent."""
last_result = results[-1]

if isinstance(last_result, RunResult):
crash_analyzer_prompt_builder = \
prompt_builder.CrashAnalyzerTemplateBuilder(
model=self.llm,
benchmark=last_result.benchmark)
prompt = crash_analyzer_prompt_builder.build_crash_analyzer_prompt(
last_result.benchmark, last_result.fuzz_target_source,
last_result.run_error, last_result.crash_func)
return prompt

logger.error("Expected a RunResult object in results list",
trial=self.trial)
return prompt_builder.CrashAnalyzerTemplateBuilder(self.llm).build([])

def _format_lldb_execution_result(
self,
process: sp.CompletedProcess,
previous_prompt: Optional[Prompt] = None) -> str:
"""Formats a prompt based on lldb execution result."""
if previous_prompt:
previous_prompt_text = previous_prompt.get()
else:
previous_prompt_text = ''
stdout = self.llm.truncate_prompt(process.stdout,
previous_prompt_text).strip()
stderr = self.llm.truncate_prompt(process.stderr,
stdout + previous_prompt_text).strip()
return (f'<lldb>\n{process.args}\n</lldb>\n'
f'<return code>\n{process.returncode}\n</return code>\n'
f'<stdout>\n{stdout}\n</stdout>\n'
f'<stderr>\n{stderr}\n</stderr>\n')

def _container_handle_lldb_command(self, response: str, tool: LLDBTool,
prompt: Prompt) -> Prompt:
"""Handles the command from LLM with lldb |tool|."""
prompt_text = ''
for command in self._parse_tags(response, 'lldb'):
prompt_text += self._format_lldb_execution_result(
tool.execute_in_screen(command), previous_prompt=prompt) + '\n'
prompt.append(prompt_text)
return prompt

def _container_handle_conclusion(self, cur_round: int, response: str,
crash_result: CrashResult) -> None:
"""Parses LLM conclusion, analysis and suggestion."""
logger.info('----- ROUND %02d Received conclusion -----',
cur_round,
trial=self.trial)

conclusion = self._parse_tag(response, 'conclusion')
if conclusion == 'Crash is caused by bug in fuzz driver.':
crash_result.true_bug = False
elif conclusion == 'Crash is caused by bug in project.':
crash_result.true_bug = True
else:
logger.error('***** Failed to match conclusion in %02d rounds *****',
cur_round,
trial=self.trial)

crash_result.insight = self._parse_tag(response, 'analysis and suggestion')
if not crash_result.insight:
logger.error('Round %02d No analysis and suggestion in conclusion: %s',
cur_round,
response,
trial=self.trial)

def _container_tool_reaction(self, cur_round: int, response: str,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could this be the same as _container_tool_reaction() in Prototyper?
If so, we can relocate that function into base_agent so that you don't have to repeat it here.

Again, future users/editors will appreciate this because they can read/modify the code in one place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is different from _container_tool_reaction() in Prototyper because the prompt is designed differently.

crash_result: CrashResult) -> Optional[Prompt]:
"""Validates LLM conclusion or executes its command."""
if self._parse_tag(response, 'conclusion'):
return self._container_handle_conclusion(cur_round, response,
crash_result)
prompt = prompt_builder.CrashAnalyzerTemplateBuilder(self.llm,
None).build([])
if self._parse_tag(response, 'lldb'):
return self._container_handle_lldb_command(response, self.analyze_tool,
prompt)
if self._parse_tag(response, 'bash'):
return self._container_handle_bash_command(response, self.check_tool,
prompt)
return None

def _copy_cloud_artifact(self, artifact_path: str) -> None:
"""Copies the artifact from cloud build."""
cloud_build_artifact_path = (
f'/workspace/{os.path.basename(artifact_path)}')
if os.path.exists(cloud_build_artifact_path):
os.makedirs(os.path.dirname(artifact_path), exist_ok=True)
shutil.copyfile(cloud_build_artifact_path, artifact_path)
logger.info('Copied artifact from %s to %s',
cloud_build_artifact_path,
artifact_path,
trial=self.trial)
else:
logger.warning('Unable to find artifact_path in cloud build: %s',
cloud_build_artifact_path,
trial=self.trial)

def execute(self, result_history: list[Result]) -> CrashResult:
"""Executes the agent based on previous run result."""
WorkDirs(self.args.work_dirs.base)
last_result = result_history[-1]
benchmark = last_result.benchmark
logger.info('Executing Crash Analyzer', trial=self.trial)
assert isinstance(last_result, RunResult)

if self.args.cloud_experiment_name:
self._copy_cloud_artifact(last_result.artifact_path)

# TODO(dongge): Move these to oss_fuzz_checkout.
generated_target_name = os.path.basename(benchmark.target_path)
sample_id = os.path.splitext(generated_target_name)[0]
generated_oss_fuzz_project = (
f'{benchmark.id}-{sample_id}-lldb-{self.trial:02d}')
generated_oss_fuzz_project = evaluator_lib.rectify_docker_tag(
generated_oss_fuzz_project)

# TODO(dongge): Write to OSS-Fuzz project dir files directly.
fuzz_target_path = os.path.join(last_result.work_dirs.fuzz_targets,
f'{self.trial:02d}.fuzz_target')
with open(fuzz_target_path, 'w') as ft_file:
ft_file.write(last_result.fuzz_target_source)
if last_result.build_script_source:
build_script_path = os.path.join(last_result.work_dirs.fuzz_targets,
f'{self.trial:02d}.build_script')
with open(build_script_path, 'w') as ft_file:
ft_file.write(last_result.build_script_source)
else:
build_script_path = ''

evaluator_lib.Evaluator.create_ossfuzz_project_with_lldb(
benchmark, generated_oss_fuzz_project, fuzz_target_path, last_result,
build_script_path, last_result.artifact_path)

self.analyze_tool = LLDBTool(benchmark,
result=last_result,
name='lldb',
project_name=generated_oss_fuzz_project)
self.analyze_tool.execute('compile > /dev/null')
self.analyze_tool.execute("'screen -dmS lldb_session bash -c 'lldb'")
self.check_tool = ProjectContainerTool(
benchmark, name='check', project_name=generated_oss_fuzz_project)
self.check_tool.compile(extra_commands=' && rm -rf /out/* > /dev/null')
prompt = self._initial_prompt(result_history)
prompt.add_problem(self.analyze_tool.tutorial())
prompt.add_problem(self.check_tool.tutorial())
crash_result = CrashResult(benchmark=benchmark,
trial=last_result.trial,
work_dirs=last_result.work_dirs,
author=self,
chat_history={self.name: ''})
cur_round = 1
try:
client = self.llm.get_chat_client(model=self.llm.get_model())
while prompt and cur_round < MAX_ROUND:
response = self.chat_llm(cur_round=cur_round,
client=client,
prompt=prompt,
trial=self.trial)
prompt = self._container_tool_reaction(cur_round, response,
crash_result)
cur_round += 1
self._sleep_random_duration(trial=self.trial)
finally:
# Cleanup: stop the container
logger.debug('Stopping the crash analyze container %s',
self.analyze_tool.container_id,
trial=self.trial)
self.analyze_tool.terminate()

return crash_result
26 changes: 20 additions & 6 deletions common/cloud_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

import utils
from agent.base_agent import BaseAgent
from results import Result
from results import Result, RunResult

OF_REPO = 'https://github.com/google/oss-fuzz.git'
OFG_ROOT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
Expand Down Expand Up @@ -107,12 +107,12 @@ def _prepare_and_upload_archive(self) -> str:
return self._upload_to_gcs(archive_path)

def _request_cloud_build(self, ofg_repo_url: str, agent_dill_url: str,
results_dill_url: str,
results_dill_url: str, artifact_url: str,
new_result_filename: str) -> str:
"""Requests Cloud Build to execute the operation."""
cloud_build_config = {
'steps': [
# Step 1: Download the dill files from GCS bucket.
# Step 1: Download the dill and artifact files from GCS bucket.
{
'name': 'bash',
'dir': '/workspace',
Expand All @@ -128,6 +128,16 @@ def _request_cloud_build(self, ofg_repo_url: str, agent_dill_url: str,
'dir': '/workspace',
'args': ['cp', results_dill_url, 'dills/result_history.pkl']
},
{
'name': 'gcr.io/cloud-builders/gsutil',
'dir': '/workspace',
'args': [
'cp', artifact_url,
f'/workspace/{os.path.basename(artifact_url)}'
],
# artifact_url only exists in crash analyzer.
'allowFailure': True,
},
# Step 2: Prepare OFG and OF repos.
{
'name':
Expand Down Expand Up @@ -196,7 +206,7 @@ def _request_cloud_build(self, ofg_repo_url: str, agent_dill_url: str,
'/workspace/dills/new_result.pkl'
],
},
# Step 4: Upload the result to GCS bucket
# Step 6: Upload the result to GCS bucket
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks!

{
'name': 'bash',
'dir': '/workspace',
Expand Down Expand Up @@ -309,11 +319,15 @@ def run(self, agent: BaseAgent, result_history: list[Result],
ofg_url = self._prepare_and_upload_archive()
agent_url = self._upload_to_gcs(agent_dill)
results_url = self._upload_to_gcs(results_dill)
last_result = result_history[-1]
artifact_url = 'ARTIFACT_URL DOES NOT EXISTS'
if isinstance(last_result, RunResult) and last_result.artifact_path:
artifact_url = self._upload_to_gcs(last_result.artifact_path)

# Step 3: Request Cloud Build.
new_result_filename = f'{uuid.uuid4().hex}.pkl'
build_id = self._request_cloud_build(ofg_url, agent_url, results_url,
new_result_filename)
artifact_url, new_result_filename)

# Step 4: Download new result dill.
cloud_build_log = ''
Expand All @@ -334,7 +348,7 @@ def run(self, agent: BaseAgent, result_history: list[Result],

cloud_build_log += self._get_build_log(build_id)

# Step 4: Deserialize dilld file.
# Step 5: Deserialize dilld file.
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍🏼

result = utils.deserialize_from_dill(new_result_dill)
if not result:
cloud_build_log += f'Failed to deserialize from dill {new_result_dill}.\n'
Expand Down
Loading