为什么gdb通过0地址显示偏移会提示地址错误
现象
在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地址显示偏移会提示地址错误的更多相关文章
- 42 Flutter仿京东商城项目  修改默认收货地址 显示默认收货地址
		
CheckOut.dart import 'package:flutter/material.dart'; import '../services/ScreenAdapter.dart'; impor ...
 - Servlet 3.0 使用注解配置URl提示404错误
		
我的环境是 Eclipse oxygen + Servlet 3.0 因为3.0已经开始使用注解了 之前我都是配置listenner 还有Servlet mapping 在 web.xml 中 就 ...
 - 路由器wan口ip地址显示0.0.0.0怎么办
		
http://m.xuexila.com/luyouqi/671049.html 这个网络时代里面我们最常用来连接网络的设备就是路由器了,现在的社会不管是工作还是生活几乎都离不开网络了,同时我们也要学 ...
 - VMware 安装 Centos7 后,没有ipv4的地址,或者地址显示127.0.0.1
		
问题描述:VMware 安装 Centos7 后,没有ipv4的地址,或者地址显示127.0.0.1 安装环境:VMware版本:VMware-workstation-full-14.0.0.2405 ...
 - 重写alert方法,去掉地址显示
		
//重写alert方法,去掉地址显示window.alert = function(name){ var iframe = document.createElement("IFRAME&qu ...
 - 百度地图api通过地址显示地图,白名单
		
百度地图api通过地址显示地图,白名单 http://developer.baidu.com/map/jsdemo.htm#i7_1?qq-pf-to=pcqq.c2c---------------- ...
 - 重写Alert和confirm方法去除地址显示
		
//重写alert方法,去掉地址显示window.alert = function(name){var iframe = document.createElement("IFRAME&quo ...
 - vc6.0如何显示行号以及出现版本不兼容问题
		
有时编译时,提示某某行有错,但是要定位到某一行的话,如果在编辑页面能够将行号显示出来,查找也就更方便了,下面我来介绍一下让VC6.0显示行号的方法. 工具/原料 VC6.0.显示行号的插件 方 ...
 - VC6.0到VS2013全部版本下载地址
		
Microsoft Visual Studio 6.0 下载:英文版360云盘下载: http://l11.yunpan.cn/lk/sVeBLC3bhumrI英文版115网盘下载: http://1 ...
 - Hbase 0.95.2介绍及下载地址
		
HBase是一个分布式的.面向列的开源数据库,该技术来源于Google论文“Bigtable:一个结构化数据的分布式存储系统”.就像Bigtable利用了Google文件系统(File System) ...
 
随机推荐
- 快速构建用户xlwings环境
			
一.安装python python-3.8.3-amd64.exe 二.准备文件requirements.txt 内容如下 安装失败需要切换国内镜像源 numpy==1.22.1 openpyxl== ...
 - Mysql习题系列(一):基本select语句与运算符
			
Mysql8.0习题系列 软件下载地址 提取码:7v7u 数据下载地址 提取码:e6p9 文章目录 Mysql8.0习题系列 1. 基本select语句 1.1 题目 1.2答案 1.查询员工12个月 ...
 - C#导出Excel设置单元格样式
			
C# 导出Excel 1 //导出Excel 2 private void ExportExcel(string fileName, System.Data.DataTable myDGV, stri ...
 - 右键无法新建word文件怎么办?
			
电脑用久了,总会出现奇奇怪怪的问题. 我最近遇到一个问题:鼠标右键无法新建word文件.如何解决此问题呢? 刚开始,我忍了.解决方法为:把word图标固定到任务栏,就好了呗,需要用的时候, ...
 - Maven使用tomcat7-maven插件部署项目问题
			
Cannot invoke Tomcat manager: Connection refused: connect -> [Help 1] 检查tomcat-users.xml的用户配置,用户权 ...
 - iOS开发之长按浮动操作选择项
			
我们一般会有长按一段文字显示气泡浮动操作选项的需求 我们常用的方法如下 cut: copy: select: selectAll: paste: delete: _promptForRep ...
 - wendows 批量修改文件后缀(含递归下级)
			
for /r %%a in (*.jpg)do ren "%%a" "%%~na.png" //-- or :for /r %a in (*.jpg)do re ...
 - 靶机练习4: SolidState
			
信息收集阶段 全端口扫描,查询目标靶机开放端口和服务 sudo nmap -p- -n -v -sS --max-retries=0 172.16.33.35 进行服务版本扫描 nmap -p22,2 ...
 - RestTemplate 设置超时时间
			
不废话直接上代码 SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); reque ...
 - 3dsmax+vray5进行分布式渲染
			
环境 我的两台电脑软件是一样的:win11 + 3ds max 2020 + vray5 update 2.2. 台式机1硬件是12900 + RTX 3080 Ti,台式机2硬件是12900KF + ...