多处收到SIGSEGV(11)错误

Receiving SIGSEGV (11) errors in many places

我在使用看似简单的代码的多平台应用程序中收到 SIGSEGV (11) 异常。每次发生这种情况时,调试器似乎都会将我带到不同的地方。所以我拼凑了一个简单的应用程序来演示这一点。它不会发生在 Windows 中,但会发生在所有其他平台上。 Windows 工作正常。这是我每次都能重新创建并在下面演示的唯一示例,来自 XSuperObject 库。

启动一个新的空白多平台应用程序并仅添加一个按钮。

DFM

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 259
  ClientWidth = 310
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object Button1: TButton
    Position.X = 136.000000000000000000
    Position.Y = 96.000000000000000000
    TabOrder = 0
    Text = 'Button1'
    OnClick = Button1Click
  end
end

现在只为此按钮的 OnClick 事件添加一个处理程序。

代码

unit uMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  XSuperObject;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  IOUtils;

procedure TForm1.Button1Click(Sender: TObject);
var
  O: ISuperObject;
  L: TStringList;
  FN: String;
begin
  FN:= TPath.Combine(TPath.GetHomePath, 'MyApp');
  ForceDirectories(FN);
  FN:= TPath.Combine(FN, 'Test.json');
  O:= SO;
  O.S['foo']:= 'bar';
  L:= TStringList.Create;
  try
    L.Text:= O.AsJSON(True);
    L.SaveToFile(FN);
  finally
    L.Free;
  end;
  O:= TSuperObject.ParseFile(FN); // <-- SIGSEGV (11) happens here
end;

end.

例如,在 OSX 的测试中,按下按钮会产生异常:

Project SIGSEGVTest raised exception class SIGSEGV (11).

当我点击 Break 时,它会将我带到行 1394 上的某个 System.Character.inc 文件,它似乎是一堆某种二进制文件:

    db  ,,[=13=],[=13=],[=13=],[=13=],[=13=],,F,,[=13=],[=13=],[=13=],[=13=],[=13=]

在某些情况下,我会反复收到此异常,因为它似乎是无限的。我似乎无法追踪它的来源。我没有任何运气来搜索这个,我找到的所有东西都是另一种语言,没有答案,或者是另一种 pascal IDE(例如 Lazarus)。时不时地,在与上述相同的情况下,应用程序只是锁定而不是给出此异常。有时,当我单击 Break 时,调试器不会带我去任何地方。

我确实理解异常的含义(本质上与访问冲突相同),但为什么我在 Windows 以外的所有平台上都会收到此异常?

注意

我过去确实设法通过使用泛型 TList<> 而不是传统的弃用 TList 在一个地方解决了这个问题。但这只是一个,我正在努力找出在这么多地方出现此异常的真正原因。已经与它斗争了几个星期了。

我正在使用 Delphi XE7 Update 2,我的 XSuperObject 副本刚刚更新到最新版本,但运气不好。

我还安装了最新的 IDE 修复包,但仍然没有成功。

更新

例如,当在 iOS 模拟器 (iOS 7.1) 中运行相同的应用程序而不进行调试时,我得到以下信息:

Access violation at address 0060907D, accessing address 00000000.

更新

这是来自 OSX 的崩溃报告(太大,放不下):

http://pastebin.com/0Tg7pNn8

更新

似乎指针不知何故被破坏了。到目前为止,在大多数情况下,当我访问一个应该初始化的指针时,就会发生这种情况。例如,当我有一个经典的 TList 时,它让我可以很好地使用它,除非我试图读取其中一个指针并遇到同样的错误。完全相同的代码在 Windows 中运行完美。例如, MyObj:= TMyObj(MyTList[0]); 在那里我可以看到正确的指针,但无论如何都会产生这个异常。不幸的是,由于我试图重现这种特殊情况,所以我做不到。

更新

我终于成功地单步执行了 XSuperObject 库(之前我在尝试设置断点单步执行时遇到了其他奇怪的问题)。它在以下构造函数内的第 587 行中断:

constructor TBaseJSON<T, Typ>.Create(JSON: String; const CheckDate: Boolean);
type PInterface = ^IInterface;
var
  JVal: IJSONAncestor;
  PIntf: PInterface;
begin
  FCheckDate := CheckDate;
  if (Self.InheritsFrom(TSuperArray)) and (Trim(JSON) = '{}') then JSON := '[]';
  JVal := TJSONObject.ParseJSONValue(JSON, FCheckDate);
  if JVal.QueryInterface(GetTypeData(TypeInfo(T)).Guid, FJSONObj) = S_OK then // <-- Happens here
     FInterface := TValue.From<T>(FJSONObj).AsInterface
  else
     FCasted := JVal
end;

这是 XSuperObject 中的一个错误。当 JSON 文件以 Unicode BOM 开头时它会崩溃。您需要保存没有 BOM 的 JSON 文件,但在 Unicode 中:

L.WriteBOM := False;
L.SaveToFile(FN, TEncoding.UTF8);

但是如果您得到一个包含 BOM 的 JSON 文件,您仍然会崩溃。为了避免这种情况,您可以按如下方式修补 TSuperObject.ParseStream:

class function TSuperObject.ParseStream(Stream: TStream): TSuperObject;
var
  Strm: TStringList;
begin
  Strm := TStringList.Create;
  try
    Strm.LoadFromStream(Stream);
    Result := TSuperObject.Create( Strm.Text);
  finally
    Strm.Free;
  end;
end;

这只是一个快速解决方法。更好的方法应该仔细查看 JSON 文件的编码。

当您的文件保存在 OSX 时,它看起来像这样:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000  EF BB BF 7B 0D 0A 20 20 22 66 6F 6F 22 3A 22 62  {..  "foo":"b
00000010  61 72 22 0D 0A 7D 0D 0A                          ar"..}..

前3个字节是BOM,这给XSuperObject带来了麻烦。发生崩溃是因为 JVal 在第 587 行第二次遇到此行时为 nil。

更新:

RFC 7159 说 json 实现不能在开头添加字节顺序标记。但如果它存在,他们可能会忽略它。因此,即使能够处理 BOM 不是必需的,在这种情况下也不应该崩溃。

应该通过查看文件来检测 JSON 编码(这在过时的 RFC 4327 中有解释)