Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
avocado
提交
993a5216
A
avocado
项目概览
openeuler
/
avocado
通知
0
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
A
avocado
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
993a5216
编写于
12月 02, 2014
作者:
L
Lucas Meneghel Rodrigues
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #280 from clebergnu/gdb_remote_v2
GDB remote protocol support [v2]
上级
7a69ae5b
0c0401b1
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
380 addition
and
6 deletion
+380
-6
avocado/gdb.py
avocado/gdb.py
+284
-1
docs/source/DebuggingWithGDB.rst
docs/source/DebuggingWithGDB.rst
+17
-5
examples/tests/gdbtest.py
examples/tests/gdbtest.py
+12
-0
selftests/all/unit/avocado/gdb_unittest.py
selftests/all/unit/avocado/gdb_unittest.py
+67
-0
未找到文件。
avocado/gdb.py
浏览文件 @
993a5216
...
...
@@ -16,9 +16,13 @@
Module that provides communication with GDB via its GDB/MI interpreter
"""
__all__
=
[
'GDB'
,
'GDBServer'
,
'GDBRemote'
]
import
os
import
time
import
fcntl
import
socket
import
tempfile
import
subprocess
...
...
@@ -29,6 +33,24 @@ GDB_PROMPT = '(gdb)'
GDB_EXIT
=
'^exit'
GDB_BREAK_CONTITIONS
=
[
GDB_PROMPT
,
GDB_EXIT
]
#: How the remote protocol signals a transmission success (in ACK mode)
REMOTE_TRANSMISSION_SUCCESS
=
'+'
#: How the remote protocol signals a transmission failure (in ACK mode)
REMOTE_TRANSMISSION_FAILURE
=
'-'
#: How the remote protocol flags the start of a packet
REMOTE_PREFIX
=
'$'
#: How the remote protocol flags the end of the packet payload, and that the
#: two digits checksum follow
REMOTE_DELIMITER
=
'#'
#: Rather conservative default maximum packet size for clients using the
#: remote protocol. Individual connections can ask (and do so by default)
#: the server about the maximum packet size they can handle.
REMOTE_MAX_PACKET_SIZE
=
1024
class
UnexpectedResponseError
(
Exception
):
...
...
@@ -38,6 +60,38 @@ class UnexpectedResponseError(Exception):
pass
class
ServerInitTimeoutError
(
Exception
):
'''
Server took longer than expected to initialize itself properly
'''
pass
class
InvalidPacketError
(
Exception
):
'''
Packet received has invalid format
'''
pass
class
NotConnectedError
(
Exception
):
'''
GDBRemote is not connected to a remote GDB server
'''
pass
class
RetransmissionRequestedError
(
Exception
):
'''
Message integrity was not validated and retransmission is being requested
'''
pass
def
parse_mi
(
line
):
'''
Parse a GDB/MI line
...
...
@@ -124,6 +178,91 @@ def is_fatal_signal(parsed_mi_msg):
return
is_sigsegv
(
parsed_mi_msg
)
or
is_sigabrt
(
parsed_mi_msg
)
def
format_as_hex
(
char
):
"""
Formats a single ascii character as a lower case hex string
:param char: a single ascii character
:type char: str
:returns: the character formatted as a lower case hex string
:rtype: str
"""
return
"%2x"
%
ord
(
char
)
def
string_to_hex
(
text
):
"""
Formats a string of text into an hex representation
:param text: a multi character string
:type text: str
:returns: the string converted to an hex representation
:rtype: str
"""
return
""
.
join
(
map
(
format_as_hex
,
text
))
def
remote_checksum
(
input
):
'''
Calculates a remote message checksum
:param input: the message input payload, without the start and end markers
:type input: str
:returns: two digit checksum
:rtype: str
'''
sum
=
0
for
i
in
input
:
sum
+=
ord
(
i
)
result
=
sum
%
256
hexa
=
"%2x"
%
result
return
hexa
.
lower
()
def
remote_encode
(
data
):
"""
Encodes a command
That is, add prefix, suffix and checksum
:param command_data: the command data payload
:type command_data: str
:returns: the encoded command, ready to be sent to a remote GDB
:rtype: str
"""
return
"$%s#%s"
%
(
data
,
remote_checksum
(
data
))
def
remote_decode
(
data
):
"""
Decodes a packet and returns its payload
:param command_data: the command data payload
:type command_data: str
:returns: the encoded command, ready to be sent to a remote GDB
:rtype: str
"""
if
data
[
0
]
!=
REMOTE_PREFIX
:
raise
InvalidPacketError
if
data
[
-
3
]
!=
REMOTE_DELIMITER
:
raise
InvalidPacketError
payload
=
data
[
1
:
-
3
]
checksum
=
data
[
-
2
:]
if
payload
==
''
:
expected_checksum
=
'00'
else
:
expected_checksum
=
remote_checksum
(
payload
)
if
checksum
!=
expected_checksum
:
raise
InvalidPacketError
return
payload
class
CommandResult
(
object
):
"""
...
...
@@ -451,8 +590,26 @@ class GDBServer(object):
#: The range from which a port to GDB server will try to be allocated from
PORT_RANGE
=
(
20000
,
20999
)
def
__init__
(
self
,
path
=
'/usr/bin/gdbserver'
,
port
=
None
,
*
extra_args
):
#: The time to optionally wait for the server to initialize itself and be
#: ready to accept new connections
INIT_TIMEOUT
=
2.0
def
__init__
(
self
,
path
=
'/usr/bin/gdbserver'
,
port
=
None
,
wait_until_running
=
True
,
*
extra_args
):
"""
Initializes a new gdbserver instance
:param path: location of the gdbserver binary
:type path: str
:param port: tcp port number to listen on for incoming connections
:type port: int
:param wait_until_running: wait until the gdbserver is running and
accepting connections. It may take a little
after the process is started and it is
actually bound to the aloccated port
:type wait_until_running: bool
:param extra_args: optional extra arguments to be passed to gdbserver
"""
self
.
path
=
path
args
=
[
self
.
path
]
args
+=
self
.
REQUIRED_ARGS
...
...
@@ -475,6 +632,25 @@ class GDBServer(object):
stderr
=
self
.
stderr
,
close_fds
=
True
)
if
wait_until_running
:
self
.
_wait_until_running
()
def
_wait_until_running
(
self
):
init_time
=
time
.
time
()
connection_ok
=
False
c
=
GDB
()
while
time
.
time
()
-
init_time
<
self
.
INIT_TIMEOUT
:
try
:
c
.
connect
(
self
.
port
)
connection_ok
=
True
break
except
:
time
.
sleep
(
0.1
)
c
.
disconnect
()
c
.
exit
()
if
not
connection_ok
:
raise
ServerInitTimeoutError
def
exit
(
self
,
force
=
True
):
"""
Quits the gdb_server process
...
...
@@ -506,3 +682,110 @@ class GDBServer(object):
self
.
process
.
wait
()
self
.
stdout
.
close
()
self
.
stderr
.
close
()
class
GDBRemote
(
object
):
def
__init__
(
self
,
host
,
port
,
no_ack_mode
=
True
,
extended_mode
=
True
):
"""
Initializes a new GDBRemote object.
A GDBRemote acts like a client that speaks the GDB remote protocol,
documented at:
https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html
Caveat: we currently do not support communicating with devices, only
with TCP sockets. This limitation is basically due to the lack of
use cases that justify an implementation, but not due to any technical
shortcoming.
:param host: the IP address or host name
:type host: str
:param port: the port number where the the remote GDB is listening on
:type port: int
:param no_ack_mode: if the packet transmission confirmation mode should
be disabled
:type no_ack_mode: bool
:param extended_mode: if the remote extended mode should be enabled
:type param extended_mode: bool
"""
self
.
host
=
host
self
.
port
=
port
# Temporary holder for the class init attributes
self
.
_no_ack_mode
=
no_ack_mode
self
.
no_ack_mode
=
False
self
.
_extended_mode
=
extended_mode
self
.
extended_mode
=
False
self
.
_socket
=
None
def
cmd
(
self
,
command_data
,
expected_response
=
None
):
"""
Sends a command data to a remote gdb server
Limitations: the current version does not deal with retransmissions.
:param command_data: the remote command to send the the remote stub
:type command_data: str
:param expected_response: the (optional) response that is expected
as a response for the command sent
:type expected_response: str
:raises: RetransmissionRequestedError, UnexpectedResponseError
:returns: raw data read from from the remote server
:rtype: str
"""
if
self
.
_socket
is
None
:
raise
NotConnectedError
data
=
remote_encode
(
command_data
)
sent
=
self
.
_socket
.
send
(
data
)
if
not
self
.
no_ack_mode
:
transmission_result
=
self
.
_socket
.
recv
(
1
)
if
transmission_result
==
REMOTE_TRANSMISSION_FAILURE
:
raise
RetransmissionRequestedError
result
=
self
.
_socket
.
recv
(
REMOTE_MAX_PACKET_SIZE
)
response_payload
=
remote_decode
(
result
)
if
expected_response
is
not
None
:
if
expected_response
!=
response_payload
:
raise
UnexpectedResponseError
return
response_payload
def
set_extended_mode
(
self
):
"""
Enable extended mode. In extended mode, the remote server is made
persistent. The 'R' packet is used to restart the program being
debugged. Original documentation at:
https://sourceware.org/gdb/current/onlinedocs/gdb/Packets.html#extended-mode
"""
self
.
cmd
(
"!"
,
"OK"
)
self
.
extended_mode
=
True
def
start_no_ack_mode
(
self
):
"""
Request that the remote stub disable the normal +/- protocol
acknowledgments. Original documentation at:
https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#QStartNoAckMode
"""
self
.
cmd
(
"QStartNoAckMode"
,
"OK"
)
self
.
no_ack_mode
=
True
def
connect
(
self
):
"""
Connects to the remote target and initializes the chosen modes
"""
self
.
_socket
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
self
.
_socket
.
connect
((
self
.
host
,
self
.
port
))
if
self
.
_no_ack_mode
:
self
.
start_no_ack_mode
()
if
self
.
_extended_mode
:
self
.
set_extended_mode
()
docs/source/DebuggingWithGDB.rst
浏览文件 @
993a5216
Debugging with GDB
==================
Avocado has support for transparently debugging binaries inside the GNU
Debugger. This means that any test that uses :mod:`avocado.utils.process`
Avocado has two levels of GDB support, one by using the Avocado GDB APIs
to fire up GDB and interact with it, and other by transparently debugging
binaries inside the GNU Debugger based on command line only options. This
later option means that any test that uses :mod:`avocado.utils.process`
can transparently inspect processes during test run time.
Usage
-----
API
---
Avocado's GDB module, provides three main classes that lets a test writer
interact with a `gdb` process, a `gdbserver` process and also use the GDB
remote protocol for interaction with a remote target.
Please refer to :mod:`avocado.gdb` for more information.
Transparent Debugging Usage
---------------------------
This feature is implemented as a plugin, that adds the `--gdb-run-bin` option
to the avocado `run` command. For a detailed explanation please consult the
avocado man page.
Caveats
-------
~~~~~~~
Currently, when using the Avocado GDB plugin, that is, when using the
`--gdb-run-bin` option, there are some caveats you should be aware of:
...
...
examples/tests/gdbtest.py
浏览文件 @
993a5216
...
...
@@ -378,6 +378,17 @@ class gdbtest(test.Test):
result
=
process
.
run
(
self
.
return99_binary_path
,
ignore_status
=
True
)
self
.
assertIn
(
"return 99
\n
"
,
result
.
stdout
)
def
test_remote
(
self
):
"""
Tests GDBRemote interaction with a GDBServer
"""
s
=
gdb
.
GDBServer
()
r
=
gdb
.
GDBRemote
(
'127.0.0.1'
,
s
.
port
)
r
.
connect
()
r
.
cmd
(
"qSupported"
)
r
.
cmd
(
"qfThreadInfo"
)
s
.
exit
()
def
action
(
self
):
"""
Execute tests
...
...
@@ -402,3 +413,4 @@ class gdbtest(test.Test):
self
.
test_server_stderr
()
self
.
test_server_stdout
()
self
.
test_interactive_stdout
()
self
.
test_remote
()
selftests/all/unit/avocado/gdb_unittest.py
0 → 100755
浏览文件 @
993a5216
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See LICENSE for more details.
#
# Copyright: Red Hat Inc. 2014
# Authors: Cleber Rosa <cleber@redhat.com>
import
os
import
sys
import
unittest
# simple magic for using scripts within a source tree
basedir
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
)))
basedir
=
os
.
path
.
dirname
(
basedir
)
if
os
.
path
.
isdir
(
os
.
path
.
join
(
basedir
,
'avocado'
)):
sys
.
path
.
append
(
basedir
)
from
avocado
import
gdb
class
GDBRemoteTest
(
unittest
.
TestCase
):
def
test_checksum
(
self
):
in_out
=
((
'!'
,
'21'
),
(
'OK'
,
'9A'
),
(
'foo'
,
'44'
))
for
io
in
in_out
:
i
,
o
=
io
self
.
assertTrue
(
gdb
.
remote_checksum
(
i
),
o
)
def
test_encode_command
(
self
):
in_out
=
((
'!'
,
'$!#21'
),
(
'OK'
,
'$OK#9a'
),
(
'foo'
,
'$foo#44'
))
for
io
in
in_out
:
i
,
o
=
io
self
.
assertTrue
(
gdb
.
remote_encode
(
i
),
o
)
def
test_decode_response
(
self
):
in_out
=
((
'$!#21'
,
'!'
),
(
'$OK#9a'
,
'OK'
),
(
'$foo#44'
,
'foo'
))
for
io
in
in_out
:
i
,
o
=
io
self
.
assertTrue
(
gdb
.
remote_decode
(
i
),
o
)
def
test_decode_invalid
(
self
):
invalid_packets
=
[
'$!#22'
,
'$foo$bar#21'
,
'!!#21'
,
'+$!#21'
]
for
p
in
invalid_packets
:
self
.
assertRaises
(
gdb
.
InvalidPacketError
,
gdb
.
remote_decode
,
p
)
if
__name__
==
'__main__'
:
unittest
.
main
()
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录