现象

在gdb中,如果想看一个struct的某个field的偏移量,和C语言一样可以通过对一个0地址变量取地址,然后取成员的偏移量获得。更神奇的地方在于和C语言一样,这里也不会触发内存访问异常。

另外还有一个奇怪的现象:对于x取地址的时候没有问题,但是对于c字段取地址之后会有一个莫名其妙的错误提示"<error: Cannot access memory at address 0x8>"。

tsecer@harry: cat -n main.cpp
1 struct tsecer
2 {
3 double x;
4 char c;
5 double d;
6 };
7
8 int main()
9 {
10 tsecer x;
11 return x.c;
12 }
tsecer@harry: gcc -g main.cpp
tsecer@harry: gdb -quiet ./a.out
Reading symbols from ./a.out...
(gdb) p &((tsecer*)0)->x
$1 = (double *) 0x0
(gdb) p &((tsecer*)0)->c
$2 = 0x8 <error: Cannot access memory at address 0x8>
(gdb)

lazy evaluate

在取一个struct的特定域(field)时,gdb其实只是计算了它的相对地址(offset)而没有真正取这个field的绝对地址。这也意味着,一个变量是由两部分(基地址+offset)而不是一部分(绝对地址)组成。

在前面的例子中,x的地址其实是通过0作为基地址加上0作为offset两部分表示。

下面是计算一个结构field值(value)的函数。可以看到,它主要是计算了offset偏移量而不是计算了绝对地址。

/// @file: gdb-10.1\gdb\value.c
/* Given a value ARG1 (offset by OFFSET bytes)
of a struct or union type ARG_TYPE,
extract and return the value of one of its (non-static) fields.
FIELDNO says which field. */ struct value *
value_primitive_field (struct value *arg1, LONGEST offset,
int fieldno, struct type *arg_type)
{
///....
else
{
/* Plain old data member */
offset += (TYPE_FIELD_BITPOS (arg_type, fieldno)
/ (HOST_CHAR_BIT * unit_size)); /* Lazy register values with offsets are not supported. */
if (VALUE_LVAL (arg1) == lval_register && value_lazy (arg1))
value_fetch_lazy (arg1); if (value_lazy (arg1))
v = allocate_value_lazy (type);
else
{
v = allocate_value (type);
value_contents_copy_raw (v, value_embedded_offset (v),
arg1, value_embedded_offset (arg1) + offset,
type_length_units (type));
}
v->offset = (value_offset (arg1) + offset
+ value_embedded_offset (arg1));
}
set_value_component_location (v, arg1);
return v;
}

这也意味着一个value的内存存储结构相对单独存储一个绝度地址会稍微复杂一些。下面的value结构中存储了基地址address和偏移量offset两个字段。

/// @file:
/* Note that the fields in this structure are arranged to save a bit
of memory. */ struct value
{
///...
/* Location of value (if lval). */
union
{
/* If lval == lval_memory, this is the address in the inferior */
CORE_ADDR address; /*If lval == lval_register, the value is from a register. */
struct
{
/* Register number. */
int regnum;
/* Frame ID of "next" frame to which a register value is relative.
If the register value is found relative to frame F, then the
frame id of F->next will be stored in next_frame_id. */
struct frame_id next_frame_id;
} reg; /* Pointer to internal variable. */
struct internalvar *internalvar; /* Pointer to xmethod worker. */
struct xmethod_worker *xm_worker; /* If lval == lval_computed, this is a set of function pointers
to use to access and describe the value, and a closure pointer
for them to use. */
struct
{
/* Functions to call. */
const struct lval_funcs *funcs; /* Closure for those functions to use. */
void *closure;
} computed;
} location {}; /* Describes offset of a value within lval of a structure in target
addressable memory units. Note also the member embedded_offset
below. */
LONGEST offset = 0;
///...

取地址

对于&这种操作,通过value_address取到值的内存地址即可,没必要真正访问内容。再强调一遍,只是计算内存地址

下面代码是返回“基地址+offset”获得地址。

CORE_ADDR
value_address (const struct value *value)
{
if (value->lval != lval_memory)
return 0;
if (value->parent != NULL)
return value_address (value->parent.get ()) + value->offset;
if (NULL != TYPE_DATA_LOCATION (value_type (value)))
{
gdb_assert (PROP_CONST == TYPE_DATA_LOCATION_KIND (value_type (value)));
return TYPE_DATA_LOCATION_ADDR (value_type (value));
} return value->location.address + value->offset;
}

为什么有错误提示

正如注释

/* For a pointer to a textual type, also print the string

pointed to, unless pointer is null. */

所说,对于一个指向文本(字符)结构的指针,除了输出地址之外,还会输出指向的字符串内容(除非指针为0),此时就会真正访问内存地址。

而对于通常的double指针就不会,所以double取地址就不会报错。

/// @file: gdb-10.1\gdb\c-valprint.c
/* Print a pointer based on the type of its target. Arguments to this functions are roughly the same as those in c_val_print.
A difference is that ADDRESS is the address to print, with embedded_offset
already added. UNRESOLVED_ELTTYPE and ELTTYPE represent the pointed type,
respectively before and after check_typedef. */ static void
print_unpacked_pointer (struct type *type, struct type *elttype,
struct type *unresolved_elttype,
const gdb_byte *valaddr, int embedded_offset,
CORE_ADDR address, struct ui_file *stream, int recurse,
const struct value_print_options *options)
{
int want_space = 0;
struct gdbarch *gdbarch = get_type_arch (type); if (elttype->code () == TYPE_CODE_FUNC)
{
/* Try to print what function it points to. */
print_function_pointer_address (options, gdbarch, address, stream);
return;
} if (options->symbol_print)
want_space = print_address_demangle (options, gdbarch, address, stream,
demangle);
else if (options->addressprint)
{
fputs_filtered (paddress (gdbarch, address), stream);
want_space = 1;
} /* For a pointer to a textual type, also print the string
pointed to, unless pointer is null. */ if (c_textual_element_type (unresolved_elttype, options->format)
&& address != 0)
{
if (want_space)
fputs_filtered (" ", stream);
val_print_string (unresolved_elttype, NULL, address, -1, stream, options);
}
else if (cp_is_vtbl_member (type))
{
/* Print vtbl's nicely. */
CORE_ADDR vt_address = unpack_pointer (type, valaddr + embedded_offset);
struct bound_minimal_symbol msymbol =
lookup_minimal_symbol_by_pc (vt_address); /* If 'symbol_print' is set, we did the work above. */
if (!options->symbol_print
&& (msymbol.minsym != NULL)
&& (vt_address == BMSYMBOL_VALUE_ADDRESS (msymbol)))
{
if (want_space)
fputs_filtered (" ", stream);
fputs_filtered (" <", stream);
fputs_filtered (msymbol.minsym->print_name (), stream);
fputs_filtered (">", stream);
want_space = 1;
} if (vt_address && options->vtblprint)
{
struct value *vt_val;
struct symbol *wsym = NULL;
struct type *wtype; if (want_space)
fputs_filtered (" ", stream); if (msymbol.minsym != NULL)
{
const char *search_name = msymbol.minsym->search_name ();
wsym = lookup_symbol_search_name (search_name, NULL,
VAR_DOMAIN).symbol;
} if (wsym)
{
wtype = SYMBOL_TYPE (wsym);
}
else
{
wtype = unresolved_elttype;
}
vt_val = value_at (wtype, vt_address);
common_val_print (vt_val, stream, recurse + 1, options,
current_language);
if (options->prettyformat)
{
fprintf_filtered (stream, "\n");
print_spaces_filtered (2 + 2 * recurse, stream);
}
}
}
}

什么时候真正从地址取值

例如,在最后print变量的时候必然是需要取内存中内容的;反之,对于&这种只取地址的操作就没有必要了。

/* See valprint.h.  */

void
print_value (value *val, const value_print_options &opts)
{
int histindex = record_latest_value (val); annotate_value_history_begin (histindex, value_type (val)); printf_filtered ("$%d = ", histindex); annotate_value_history_value (); print_formatted (val, 0, &opts, gdb_stdout);
printf_filtered ("\n"); annotate_value_history_end ();
} /* Access to the value history. */ /* Record a new value in the value history.
Returns the absolute history index of the entry. */ int
record_latest_value (struct value *val)
{
/* We don't want this value to have anything to do with the inferior anymore.
In particular, "set $1 = 50" should not affect the variable from which
the value was taken, and fast watchpoints should be able to assume that
a value on the value history never changes. */
if (value_lazy (val))
value_fetch_lazy (val);
/* We preserve VALUE_LVAL so that the user can find out where it was fetched
from. This is a bit dubious, because then *&$1 does not just return $1
but the current contents of that location. c'est la vie... */
val->modifiable = 0; value_history.push_back (release_value (val)); return value_history.size ();
}

更直观的例子

如果把char* 类型强转为int,因为print看到的是int类型而不是char * 类型,所以gdb不会尝试输出字符串的内容,进而不会有这个报错了。

(gdb) p (char*)1
$5 = 0x1 <error: Cannot access memory at address 0x1>
(gdb) p (int)(char*)1
$6 = 1
(gdb)

为什么gdb通过0地址显示偏移会提示地址错误的更多相关文章

  1. 42 Flutter仿京东商城项目 修改默认收货地址 显示默认收货地址

    CheckOut.dart import 'package:flutter/material.dart'; import '../services/ScreenAdapter.dart'; impor ...

  2. Servlet 3.0 使用注解配置URl提示404错误

    我的环境是  Eclipse oxygen + Servlet 3.0 因为3.0已经开始使用注解了 之前我都是配置listenner 还有Servlet mapping  在 web.xml 中 就 ...

  3. 路由器wan口ip地址显示0.0.0.0怎么办

    http://m.xuexila.com/luyouqi/671049.html 这个网络时代里面我们最常用来连接网络的设备就是路由器了,现在的社会不管是工作还是生活几乎都离不开网络了,同时我们也要学 ...

  4. VMware 安装 Centos7 后,没有ipv4的地址,或者地址显示127.0.0.1

    问题描述:VMware 安装 Centos7 后,没有ipv4的地址,或者地址显示127.0.0.1 安装环境:VMware版本:VMware-workstation-full-14.0.0.2405 ...

  5. 重写alert方法,去掉地址显示

    //重写alert方法,去掉地址显示window.alert = function(name){ var iframe = document.createElement("IFRAME&qu ...

  6. 百度地图api通过地址显示地图,白名单

    百度地图api通过地址显示地图,白名单 http://developer.baidu.com/map/jsdemo.htm#i7_1?qq-pf-to=pcqq.c2c---------------- ...

  7. 重写Alert和confirm方法去除地址显示

    //重写alert方法,去掉地址显示window.alert = function(name){var iframe = document.createElement("IFRAME&quo ...

  8. vc6.0如何显示行号以及出现版本不兼容问题

    有时编译时,提示某某行有错,但是要定位到某一行的话,如果在编辑页面能够将行号显示出来,查找也就更方便了,下面我来介绍一下让VC6.0显示行号的方法.   工具/原料   VC6.0.显示行号的插件 方 ...

  9. VC6.0到VS2013全部版本下载地址

    Microsoft Visual Studio 6.0 下载:英文版360云盘下载: http://l11.yunpan.cn/lk/sVeBLC3bhumrI英文版115网盘下载: http://1 ...

  10. Hbase 0.95.2介绍及下载地址

    HBase是一个分布式的.面向列的开源数据库,该技术来源于Google论文“Bigtable:一个结构化数据的分布式存储系统”.就像Bigtable利用了Google文件系统(File System) ...

随机推荐

  1. ASP.net EF动态映射实体

    1.配置EF与建立实体模型这里不做过多介绍.主要介绍如何动态映射实体模型 1.1.实现过程有很多种方式我们这里使用接口.然后扫描所有继承了该接口的实体类然后映射(也可以自行扩展使用特性) 首先我们新建 ...

  2. shell脚本程序的撰写

    一.让我们浏览一下整体步骤第一步,你要先用ll命令来列出该目录中都包含那些文件(目的是为了防止文件重名不利于查找等等).第二步 ,使用命令touch来建立一个shell脚本文件 , 后缀名为.sh : ...

  3. Asp.NET core/net 5接口返回实体含有long/int64的属性序列后最后几位变为0的解决

    Asp.NET core /net 5接口返回实体含有long/int64的属性时,序列后最后几位变为0的. 不得不吐槽一下MS,这种事还有问题,NND. 解决方案在startup.cs中添加:opt ...

  4. vue项目,本地启动时,请求地址会自动加上http://localhost:

    wepack设置时,使用代理 devServer: { hot: true, host: '0.0.0.0', port: "2023", // 项目启动端口, 默认8080 // ...

  5. 硬件IIC的重映射使用问题

    目录 沁恒的蓝牙系列芯片,有映射硬件模块去其他引脚的功能,可以配置各芯片的功能引脚重映射寄存器(R16_PIN_ALTERNATE),或者使用函数GPIOPinRemap函数进行配置. 比如说想要配置 ...

  6. python菜鸟学习: 11. 装饰器的基础用法

    # -*- coding: utf-8 -*-# decorator# 定义:本质是函数,就是为其他函数添加附件功能# 原则:# 1.不能修改被装饰的函数的源代码# 2.不能修改被装饰的函数的调用方式 ...

  7. Metasploit渗透测试框架一

    Metasploit简介 Metasploit是一个渗透测试平台,使您能够查找,利用和验证漏洞.该平台还有Metasploit Pro. Metasploit是一个免费的.可下载的框架,本身自带数百已 ...

  8. 10,java双向链表基础代码复现

    双向链表总体来说和单链表差别不大,最大的区别就是node结构中多了一个pre指针(变量)指向前一个节点,因为有了之前的单链表基础,双向链表的复现问题少了很多,基本可以不参考老师的代码自主写下来. 1. ...

  9. QDir类和方法应用

    QDir类提供了访问系统目录结构及其内容的与平台无关的方式. 头文件:#include <qdir.h> QDir类用来操作路径名及底层文件系统,获取关于目录路径及文件的相关信息,也可以用 ...

  10. 有道翻译-JS逆向-api调用

    简单方法 -调用开源api 这个比较简单四行代码就可以搞定,先放代码: 1 import requests 2 while True: 3 input_data = input('请输入你要翻译的数据 ...