C 言語で開発して実行していると、どうしてもポインター・エラーの発生は抑えることができない。
あるいは開発時点では予測できなかったエラーが発生することは珍しくはない。
このような予測できない障害に対しては
volatile _INTRPT_Hndlr_Parms_T ca; #pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE, \ _CTLA_HANDLE) : ( 演算命令処理 ) : MONMSG: #pragma disable_handler
のようにして例外ハンドラー ca を定義しておいて、エラーが発生したら MONMSG: にジャンプさせることができる。
MONMSG: 以降の処理では API: QMHRCVPM 等によってエラー・メッセージの詳細を取得することができるが、
最大の問題は
: ( 演算命令処理 ) :
の、どのステートメントにおいてエラーが発生したのかを調べることである。
System i の C/400 は ANSI-C 準拠であるので、エラーが発生した場所(ソース・ステートメント)を
特定することは困難である。
MONMSG: にジャンプした後ではスタックは既に更新されてしまっているので、スタックを調べても
エラー行を抽出することができないからである。
また、マクロ __LINE__ も、それを実行するステートメントのソース行が出力されるだけであるので
やはりソース行を特定することはできない。
ANSI-C でも言語仕様自体にはエラー行を取得する機能は用意されていない。
そして、このエラー・ステートメント行を特定する方法は、永年に渡って調べてきたが見つからなかった。
しかしエラー行をいち早く、特定しないことには問題の解決は早まらない。
エラーが発生したときに、どの行でエラーが発生しているのかを特定するのに、最も時間が消費されている。
エラーを修正することよりもエラーの場所を特定するために時間が費やされてきたのである。
逆にエラー行をいち早く、特定することができれば、将来に渡ってどのような問題が発生したとしても
デバッグの時間を大幅に短縮することができるのである。
しかし、本日、ここに紹介する方法は System i においてのみ C/400のエラー行を特定することができる方法である。
この方法によって障害の発生したエラー行を特定することができれば、デバッグの時間を大幅に節約することが
できる。本来、新規開発よりはデバッグのほうが大量の時間を必要とするものである。
火の中でも C/400 によって開発された適用業務では、エラー行の特定に最も時間がかかる。
従ってこのテクニックは C/400 の開発においていかに効果的であるか、おわかりであると思う。
① System i は実行ステートメントをメッセージで知らせている。
WRKACTJOB の各ジョブのメッセージや QSYSOPR に報告されているメッセージを 「F1=ヘルプ」 によって
拡大表示させてから「F9= メッセージ詳細の表示」を行うと次のように 送信先プロシージャー や
送信先ステートメント が表示される。
例外エラーが発生したときには、これらがエラーの発生行となるのである。
② 例外ハンドラー ca には、エラーを知らせるメッセージを
特定することができるメッセージ・キーが含まれている。
volatile _INTRPT_Hndlr_Parms_T ca;
は、つぎのような構造体として定義されている。
typedef _Packed struct { unsigned int Block_Size; /* Size of the parameter block */ _INVFLAGS_T Tgt_Flags; /* Target invocation flags */ char reserved[8]; /* reserved */ _INVPTR Target; /* Current target invocation */ _INVPTR Source; /* Source invocation */ _SPCPTR Com_Area; /* Communications area */ char Compare_Data[32]; /* Compare Data */ char Msg_Id[7]; /* Message ID */ char reserved1; /* 1 byte pad */ _INTRPT_Mask_T Mask; /* Interrupt class mask */ unsigned int Msg_Ref_Key; /* Message reference key */ unsigned short Exception_Id; /* Exception ID */ unsigned short Compare_Data_Len; /* Length of Compare Data */ char Signal_Class; /* Internal signal class */ char Priority; /* Handler priority */ short Severity; /* Message severity */ char reserved3[4]; /* reserved */ int Msg_Data_Len; /* Len of available message data */ char Mch_Dep_Data[10]; /* Machine dependent date */ char Tgt_Inv_Type; /*Invocation type (in MIMCHOBS.H)*/ _SUSPENDPTR Tgt_Suspend; /* Suspend pointer of target */ char Ex_Data[48]; /* First 48 bytes of excp. data */ } _INTRPT_Hndlr_Parms_T;
このうち、最も注目したいのが
unsigned int Msg_Ref_Key; /* Message reference key */
である。これは4バイトのメッセージ参照キーである。
これによってエラーが発生したときに OS400 が発生させたエラー・メッセージを具体的に特定することができる。
メッセージ取得 API : QMHRCVPM によってメッセージ・キーを指定して取得することかできるからである。
③ メッセージ取得API : QMHRCVPM の RCVM0300 形式で
ステートメント番号または命令番号を取得することができる。
QMHRCVPM によって RCVM0300 形式でメッセージを取得すると、その取得した構造体の中にはエラーを発生させた、
- プログラム名
- モジュール名 ( C/400の関数名 )
- ステートメント番号 ( C/400 関数内でのソース・ステートメント番号 )
が含まれているので、求めるエラー発生のソース行を特定することができる。
( QMHRCVPM の詳細は OS400 システムAPI解説書を参照のこと。)
■ MONMSG: エラー行を特定できるエラー監視プログラム の紹介
それでは、EnterpriseServer 6.0 に搭載された、エラー監視プログラム MONMSG を紹介しよう。
【 MONMSG 】
------------------------------------------------------------------------------------ 0001.00 /********************************************************************/ 0002.00 /* */ 0003.00 /* MONMSG : モニター・メッセージ */ 0004.00 /* */ 0005.00 /* Office Quattro Co,.Ltd 2010/07/31 13:36:27 created */ 0006.00 /* */ 0007.00 /* このプログラムは親プログラムの例外エラーが発生したとき */ 0008.00 /* 呼び出されて親プログラムのエラー発生ステートメントを */ 0009.00 /* 検出することができる。 */ 0010.00 /* */ 0011.00 /********************************************************************/ 0012.00 #pragma comment(COPYRIGHT, "as400-net.com EnterpriseServer (C) CopyRight \ 0013.00 Office Quattro.Corp. 2010- All right reserved. Users Restricted \ 0014.00 Rights - Use, duplication or disclosure restricted by Office Quattro \ 0015.00 Corp. Licenced Materials-Property of Office Quattro.") 0016.00 #include <stdio.h> 0017.00 #include <stdlib.h> 0018.00 #include <string.h> 0019.00 #include <qmhrcvpm.h> 0020.00 #include <micomput.h> /* triml */ 0021.00 #include <errno.h> 0022.00 #include <signal.h> 0023.00 #include <QMHSNDPM.h> 0024.00 0025.00 #define TRUE 0 0026.00 #define FALSE -1 0027.00 typedef struct { 0028.00 int BYTESPRO; 0029.00 int BYTESAVL; 0030.00 char MSGID[7]; 0031.00 char RESRVD; 0032.00 char EXCPDATA[100]; 0033.00 } ERRSTRUCTURE; /* Define the error return structure */ 0034.00 ERRSTRUCTURE errcode;/* Error Code Structure for RCVMSG */ 0035.00 /*************************************************************/ 0036.00 /* 内 部 使 用 関 数 */ 0037.00 /*************************************************************/ 0038.00 void GetParam(int argc, char *argv[]); 0039.00 void INZSR(void); 0040.00 /*************************************************************/ 0041.00 /* IMPORT 関 数 */ 0042.00 /*************************************************************/ 0043.00 /*************************************************************/ 0044.00 /* IMPORT 変 数 */ 0045.00 /*************************************************************/ 0046.00 /*************************************************************/ 0047.00 /* 外 部 呼 出 し 関 数 */ 0048.00 /*************************************************************/ 0049.00 void RtvJobA(char[], char[], char[], char[], char[], char[], 0050.00 char[], char[], char[], char[], char[], char[], char[], 0051.00 char[], char[]); 0052.00 #pragma map(RtvJobA, "ASNET.COM/RTVJOBA") /*CLP*/ 0053.00 #pragma linkage(RtvJobA, OS) 0054.00 char job[10], user[10], jobnbr[6], outq[10], outqlib[10], date[6]; 0055.00 char type[1], prtdev[10], langid[3], cntryid[2], ccsid[5]; 0056.00 char dftccsid[5], cymddate[7], sbmmsgq[10], sbmmsgqlib[10]; 0057.00 void ErrorResponse(char* cgi, char* err_at, char* msgid, char* msgf, 0058.00 char* msglib, char* msgtxt, char* msg, int errcode, 0059.00 int ACCLOG, int bESCAPE); 0060.00 #pragma linkage(ErrorResponse, OS) 0061.00 #pragma map(ErrorResponse, "ASNET.COM/ERRRESPONS") 0062.00 /*************************************************************/ 0063.00 /* グ ロ ー バ ル 変 数 */ 0064.00 /*************************************************************/ 0065.00 /*------( 受取りパラメータ値 )----------*/ 0066.00 volatile _INTRPT_Hndlr_Parms_T ca; 0067.00 /*------( 受取りパラメータ値 )----------*/ 0068.00 typedef struct { 0069.00 Qmh_Rcvpm_RCVM0300_t rcvm0300; 0070.00 char Msg[5000]; 0071.00 } RCVM0300; 0072.00 RCVM0300 rcvm0300; 0073.00 typedef struct { 0074.00 Qmh_Rcvpm_Sender_t sender; 0075.00 char procinfo[1024]; 0076.00 } SENDER; 0077.00 SENDER sender; 0078.00 int bBATCH , bACCLOG; 0079.00 0080.00 /********************************************************************/ 0081.00 /* m a i n --- main module of this pgm */ 0082.00 /* */ 0083.00 /* <PARAMETER> 1. CA */ 0084.00 /* */ 0085.00 /*------------------------------------------------------------------*/ 0086.00 0087.00 int main(int argc, char *argv[]){ 0088.00 int len, rcvlen, wait_time = 0, sender_offset, sender_length, 0089.00 msgsev, msg_offset, msg_length, help_offset, help_length, 0090.00 data_offset, data_length; 0091.00 char pgm[13], module[13], proc[257], stmt[31], msgid[8], msgf[11], 0092.00 msglib[11], msgtxt[133], msg[3001], msgdta[257], err_place[257]; 0093.00 0094.00 GetParam(argc, argv); /*[ パラメータの取得 ]*/ 0095.00 INZSR(); /*[ 初期設定 ]*/ 0096.00 0097.00 /*( 1 ) メッセージ・キーと同じエラー・メッセージを受信する */ 0098.00 memset(&rcvm0300, 0, sizeof(RCVM0300)); 0099.00 rcvlen = sizeof(RCVM0300); 0100.00 QMHRCVPM(&rcvm0300, rcvlen, "RCVM0300", "* ",0, 0101.00 "*ANY ", (void*)&ca.Msg_Ref_Key, wait_time,"*SAME ", 0102.00 &errcode); 0103.00 0104.00 /*( 2 ) エラー・メッセージの内容を取得する */ 0105.00 memcpy(msgid, rcvm0300.rcvm0300.Message_Id, 7); 0106.00 msgid[7] = 0x00; 0107.00 msgsev = rcvm0300.rcvm0300.Message_Severity; 0108.00 memcpy(msgf, rcvm0300.rcvm0300.Message_File_Name, 10); 0109.00 msgf[10] = 0x00; 0110.00 memcpy(msglib, rcvm0300.rcvm0300.Message_File_Library, 10); 0111.00 msglib[10] = 0x00; 0112.00 data_offset = 0; 0113.00 data_length = rcvm0300.rcvm0300.Length_Data_Returned; 0114.00 if(data_length > 0){/* メッセージ・データ */ 0115.00 memcpy(msgdta, rcvm0300.Msg, data_length); 0116.00 msgdta[data_length] = 0x00; 0117.00 }/* メッセージ・データ */ 0118.00 else msgdta[0] = 0x00; 0119.00 msg_offset = rcvm0300.rcvm0300.Length_Data_Returned; 0120.00 msg_length = rcvm0300.rcvm0300.Length_Message_Available; 0121.00 if(msg_length > 0){/* メッセージ */ 0122.00 memcpy(msgtxt, &(rcvm0300.Msg[msg_offset]), msg_length); 0123.00 msgtxt[msg_length] = 0x00; 0124.00 }/* メッセージ */ 0125.00 else msgtxt[0] = 0x00; 0126.00 help_offset = rcvm0300.rcvm0300.Length_Data_Returned + 0127.00 rcvm0300.rcvm0300.Length_Message_Returned; 0128.00 help_length = rcvm0300.rcvm0300.Length_Help_Available; 0129.00 if(help_length > 0){/* ヘルプ */ 0130.00 memcpy(msg, &(rcvm0300.Msg[help_offset]), help_length); 0131.00 msg[help_length] = 0x00; 0132.00 }/* ヘルプ */ 0133.00 else msg[0] = 0x00; 0134.00 0135.00 /*( 3 ) 送信者の情報を取得する */ 0136.00 sender_offset = rcvm0300.rcvm0300.Length_Data_Returned + 0137.00 rcvm0300.rcvm0300.Length_Message_Returned + 0138.00 rcvm0300.rcvm0300.Length_Help_Returned; 0139.00 sender_length = rcvm0300.rcvm0300.Length_Send_Returned; 0140.00 memcpy((char*)&sender, &(rcvm0300.Msg[sender_offset]), sender_length); 0141.00 memcpy(pgm, sender.sender.Send_Program_Name, 12); 0142.00 pgm[12] = 0x00; 0143.00 memcpy(module, sender.sender.Send_Module_Name, 10); 0144.00 module[10] = 0x00; 0145.00 memcpy(proc, sender.sender.Receive_Procedure_Name, 256); 0146.00 proc[256] = 0x00; 0147.00 len = triml(proc, ' '); 0148.00 if(len > 0) proc[len] = 0x00; 0149.00 memcpy(stmt, sender.sender.Receive_Statements, 30); 0150.00 stmt[30] = 0x00; 0151.00 len = triml(stmt, ' '); 0152.00 if(len > 0) stmt[len] = 0x00; 0153.00 0154.00 /*( 4 ) モニターしたメッセージを送信する */ 0155.00 if(bBATCH == TRUE){/* バッチ環境 -WEB へ返信 */ 0156.00 sprintf(err_place, 0157.00 " モジュール :%s プロシージャー :%s ステートメント %s", 0158.00 module, proc, stmt); 0159.00 ErrorResponse(pgm, err_place, msgid, msgf, msglib, 0160.00 msgtxt, msg, 504, bACCLOG, TRUE); 0161.00 }/* バッチ環境 -WEB へ返信 */ 0162.00 else{/* 対話式環境 */ 0163.00 printf(" プログラム :%s\n", pgm); 0164.00 printf(" モジュール :%s\n", module); 0165.00 printf(" プロシージャー :%s\n", proc); 0166.00 printf(" ステートメント :%s で次のエラーが発生しました。 \n", stmt); 0167.00 printf(" * ---\n"); 0168.00 printf(" メッセージ ID = %s - %s/%s\n", msgid, msglib, msgf); 0169.00 printf(" メッセージ = %s\n", msgtxt); 0170.00 printf(" ヘルプ・テキスト = %s\n", msg); 0171.00 getchar(); 0172.00 }/* 対話式環境 */ 0173.00 } 0174.00 /*************************************/ 0175.00 void GetParam(int argc, char *argv[]) 0176.00 /*************************************/ 0177.00 { 0178.00 memcpy((char*)&ca, argv[1], sizeof(_INTRPT_Hndlr_Parms_T)); 0179.00 } 0180.00 /****************/ 0181.00 void INZSR(void) 0182.00 /****************/ 0183.00 { 0184.00 char* str; 0185.00 0186.00 errcode.BYTESPRO = sizeof(errcode); 0187.00 errcode.BYTESAVL = 0; 0188.00 memcpy(job, "* ", 10); 0189.00 RtvJobA(job, user, jobnbr, outq, outqlib, date, 0190.00 type, prtdev, langid, cntryid, ccsid, dftccsid, cymddate, 0191.00 sbmmsgq, sbmmsgqlib); 0192.00 if(strncmp(type, "1", 1) == 0) bBATCH = FALSE; /* 対話式 */ 0193.00 else bBATCH = TRUE; 0194.00 str = getenv("ACCLOG"); 0195.00 if(str != NULL && strncmp(str, "*YES", 4) == 0) bACCLOG = TRUE; 0196.00 else bACCLOG = FALSE; 0197.00 } ------------------------------------------------------------------------------------
【 解説 】
MONMSG は GetParm 関数で 例外ハンドラー ca を直接、パラメータとし受け取る。
外部呼出し関数として ASNET.COM/RTVJOBA を呼び出しているが、これは MONMSG が
対話式環境で呼び出されたのか、それともバッチ・ジョブとして呼び出されたのかを判断するための、
小さな CLP であるので、読者では不要である。注目すべきは、
/*( 1 ) メッセージ・キーと同じエラー・メッセージを受信する */ QMHRCVPM(&rcvm0300, rcvlen, "RCVM0300", "* ",0, "*ANY ", (void*)&ca.Msg_Ref_Key, wait_time,"*SAME ", &errcode);
で、ca.Msg_Ref_Key を直接、メッセージ・キーとして指定していることである。
さらに、
/*( 3 ) 送信者の情報を取得する */ : memcpy((char*)&sender, &(rcvm0300.Msg[sender_offset]), sender_length);
によって送信者情報を受け取って、
memcpy(pgm, sender.sender.Send_Program_Name, 12);<----(プログラム名の取得) memcpy(module, sender.sender.Send_Module_Name, 10);<----(モジュール名の取得) memcpy(proc, sender.sender.Receive_Procedure_Name, 256);<---(プロシージャー名の取得) memcpy(stmt, sender.sender.Receive_Statements, 30);<---(ステートメントの取得)
を行っている。
このサンプル・ソース MONMSG ではブラウザへエラー・メッセージを送出しているが
どのようにでも処理することができる。
■ TESTMON : プログラム MONMSG のテスト用プログラム
実際、MONMSG をどのように呼び出して利用すべきであるのかを示すための
小さな サンプル・プログラム : TESTMON を紹介する。
【 TESTMON 】
--------------------------------------------------------------------------------------- 0001.00 #include <stdio.h> 0002.00 #include <stdlib.h> 0003.00 #include <string.h> 0004.00 #include <signal.h> 0005.00 #include <errno.h> 0006.00 0007.00 #define TRUE 0 0008.00 #define FALSE -1 0009.00 volatile _INTRPT_Hndlr_Parms_T ca; 0010.00 0011.00 void MonitorMSG(_INTRPT_Hndlr_Parms_T ca); 0012.00 #pragma linkage(MonitorMSG, OS) 0013.00 #pragma map(MonitorMSG, "ASNET.COM/MONMSG") 0014.00 0015.00 void main(void){ 0016.00 int su_a, su_b, su_c; 0017.00 0018.00 #pragma exception_handler(MONMSG, ca, 0, _C2_MH_ESCAPE, \ 0019.00 _CTLA_HANDLE) 0020.00 printf("** TESTMON **\n"); 0021.00 getchar(); 0022.00 su_a = 100; 0023.00 su_b = 0; 0024.00 su_c = su_a / su_b; 0025.00 printf("su_c = %d\n", su_c); 0026.00 getchar(); 0027.00 0028.00 MONMSG: 0029.00 #pragma disable_handler 0030.00 printf("*ERR OCCURD *\n"); 0031.00 getchar(); 0032.00 MonitorMSG(ca); 0033.00 return; 0034.00 0035.00 } ---------------------------------------------------------------------------------------
【 解説 】
ここでは 0による除算のエラーが発生するように意図的に
0024.00 su_c = su_a / su_b;
によってエラーを発生させている。
実行結果は次のとおりである。