読者です 読者をやめる 読者になる 読者になる

GDBの結果を無闇に信用してはいけない

Geant4であるプログラムを書いていたところ、segmentation faultとGDBの出力結果ではまりました。

以下のコードをstepping actionの中で使おうとしたところ、

void SteppingAction::UserSteppingAction(const G4Step* step)
{
  RunAction* action = (RunAction*)G4RunManager::GetRunManager()->GetUserRunAction();

  G4Track* track = step->GetTrack();
  G4StepPoint* prePoint = step->GetPreStepPoint();
  G4StepPoint* postPoint= step->GetPostStepPoint();
  G4VPhysicalVolume* prePV = prePoint->GetPhysicalVolume();
  G4VPhysicalVolume* postPV= postPoint->GetPhysicalVolume();

  G4OpBoundaryProcessStatus boundaryStatus = Undefined;
  static G4OpBoundaryProcess* boundaryProcess = 0;

  //find the boundary process only once
  if(!boundaryProcess){
    G4ProcessManager* pm = step->GetTrack()->GetDefinition()->GetProcessManager();
    G4int nprocesses = pm->GetProcessListLength();
    G4ProcessVector* pv = pm->GetProcessList();
    for(G4int i = 0; i < nprocesses; i++){
      if((*pv)[i]->GetProcessName() == "OpBoundary"){
        boundaryProcess = (G4OpBoundaryProcess*)(*pv)[i];
        break;
      } // if
    } // i
  } // if

  G4double stepLength = step->GetStepLength();
  G4String preVolumeName = prePoint->GetPhysicalVolume()->GetName();
  G4String postVolumeName = postPoint->GetPhysicalVolume()->GetName();
  G4String processName = postPoint->GetProcessDefinedStep()->GetProcessName();

なぜかsegmentation faultでこける場合がありました。GDBを走らせると、seg faultの原因箇所は

      if((*pv)[i]->GetProcessName() == "OpBoundary"){

ということだったのでGetProcessName()の結果をG4Stringに詰め直したり、stderrに吐いてみたりと色々試したのですが、どうやらGetProcessName()の結果自体は正しい値の入ったG4Stringを返していました。G4Stringはstd::stringの派生classなので、変なbugが残っているはずもなく、またMacでもLinuxでも同じ死に方をします。

OS X Leopardの場合は、

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000024
0x928c3401 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string ()
(gdb) bt
#0  0x928c3401 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string ()
#1  0x00008845 in G4VProcess::GetProcessName () at G4VProcess.hh:51
#2  0x00008845 in SteppingAction::UserSteppingAction (this=0x3285700, step=0x324b850) at src/SteppingAction.cc:42
#3  0x001b9bc6 in G4SteppingManager::Stepping () at basic_string.h:227
#4  0x001c0f22 in G4TrackingManager::ProcessOneTrack () at basic_string.h:227
#5  0x0019b6b4 in G4EventManager::DoProcessing () at basic_string.h:227
#6  0x000f0582 in G4RunManager::DoEventLoop () at basic_string.h:227
#7  0x000f17e1 in G4RunManager::BeamOn () at basic_string.h:227
#8  0x000f4155 in G4RunMessenger::SetNewValue () at basic_string.h:227
#9  0x0031d5c1 in G4UIcommand::DoIt () at basic_string.h:227
#10 0x003248a6 in G4UImanager::ApplyCommand () at basic_string.h:227
#11 0x000e1421 in G4UIterminal::ExecuteCommand () at basic_string.h:227
#12 0x000e29f3 in G4UIterminal::SessionStart () at basic_string.h:227
#13 0x00002d30 in main () at MinimumTest1.cc:54

のようなGDBの出力結果です。src/SteppingAction.cc:42の箇所が、

      if((*pv)[i]->GetProcessName() == "OpBoundary"){

に該当しています。

半日以上悩んだものの解決方法が見えず、まずは他の場所の修正を先にやろうと他の箇所のNULL checkをちゃんとするよう、以下のように変更にしたところ、

  G4double stepLength = step->GetStepLength();
  G4String preVolumeName = prePoint->GetPhysicalVolume()->GetName();
  G4String postVolumeName = postPoint->GetPhysicalVolume() ? postPoint->GetPhysicalVolume()->GetName() : "";
  G4String processName = postPoint->GetProcessDefinedStep() ? postPoint->GetProcessDefinedStep()->GetProcessName() : "";

なぜかseg faultが発生しなくなりました。

実はこけていた場所はGetProcessName()のところではなく、

postPoint->GetProcessDefinedStep()

の返り値がNULLだったものにGetProcessName()でaccessしていたからという落ち。

何行も後ろのbugの結果を、GDBが実際とは異なる行番号で報告してくれる場合があるわけで、これはdebug時に非常に混乱します。ただし、MacでもLinuxでも同じ報告をしてくれたので、もしかしたら自分の間違いがどこかにあるのかもしれません。