如何在嵌入式Linux产品中做立体、覆盖产品生命期的调试 (5)

2019-07-12 18:41发布

  上接:如何在嵌入式Linux产品中做立体、覆盖产品生命期的调试 ( 4)   这篇谈谈error code的使用和做法: 程序不出错则大家都好,出错了,首先找谁,error code!   大家知道,嵌入式Linux产品的软件一般有个分层的: 从上到下依次为: application middleware/Enabler driver kernel   每一层都会有error code, 下层的error code提供给上层调用时用; 所以说,上面一层的代码调用下面一层的API时,如果该API提供了error code 则说明这个API还是较好的,如果没有,对于你纠错帮助不大。   下面我举几个常用的: application调用glib的函数:       application调用sqlite的函数:   //sqlite提供error 的手段比较多 1.       直接返回错误原因 2.       返回错误码,有个函数可以根据错误码提取出错原因: 这个函数是sqlite里面比较亲切的函数:const char *sqlite_error_string (int rc); 多用这个函数,你会发现sqlite原来这么简单;   sqlite算是在嵌入式Linux产品中用的较广泛的数据库了,这个开源项目提供的error code/error string是继承自Glib, 并发扬广大了;       application调用gconf   //原型 gboolean gconf_init GError **error;   GError *error = NULL; if (!gconf_init (&error)) { fprintf (stderr, “Failed to initialize gconf, reason: %s/n”, error->message); g_error_free (error); error = NULL; }   //gconf系统里面,主要复制了GError的一套内容,并且把出错信息填充到GError中, 这样调用者就可以很好的获取出错信息;   gconf也是嵌入式Linux产品中使用的较多的用于记录setting的开源项目,对于出错的处理,继承自Glib,并没有向Sqlite那样做了扩充,应该说是中规中矩吧,也可以了。       上面的sqlite/gconf/dbus/gstreamre等都是使用比较多的开源软件,本省提供的error codeerror string也是比较好的。   如果我们自己写程序,比如写一些middleware这一层的代码,就需要给上层的application提供足够好的error code的支持。   下面就看看如何在middleware层增加对error code的很好支持,首先研究4个开源项目的error code的做法: A) sqlite, B) gconf C) gstreamer D) dbus   A) sqlite的做法      ==================sqlite error code 做法============= Sqliteerror code的做法比较简单、直白!没有什么花花肠子。 就是error code + error string.   1 定义error code(也称return code, rc)的枚举   /* ** Return values for sqlite3_exec() and sqlite3_step() */ #define SQLITE_OK           0   /* Successful result */ /* beginning-of-error-codes */ #define SQLITE_ERROR        1   /* SQL error or missing database */ #define SQLITE_INTERNAL     2   /* NOT USED. Internal logic error in SQLite */ // … 参考sqlite.h.in文件,编译后放在sqlite.h #define SQLITE_DONE        101 /* sqlite3_step() has finished executing */ /* end-of-error-codes */   2 定义与error code对应的字串   const char *sqlite3ErrStr(int rc){  const char *z;  switch( rc & 0xff ){     case SQLITE_ROW:     case SQLITE_DONE:     case SQLITE_OK:         z = "not an error";                          break;     case SQLITE_ERROR:      z = "SQL logic error or missing database";   break;    // 参考sqlite/src/main.c        default:                z = "unknown error";                         break;  }  return z; }     3 sqliteGError的关系不大,另外sqlite还有一个很有个性的东西: 提取“最近”发生的错误码或者错误信息! 这三个可是sqlite调试的大功臣啊!当调用sqlite的函数发生错误时,不要客气,调用这几个函数去查查错误原因,事半功倍! const char *sqlite3_errmsg(sqlite3 *db) const void *sqlite3_errmsg16(sqlite3 *db) int sqlite3_errcode(sqlite3 *db)       B) gconf的做法   ==================gconf error code 做法============= Gconf的error code最没有个性,就是想方设法满足GError! 往GError中填充信息。   1 定义gconf中可能出现的错误的种类,这里使用的是枚举 /* Sync with ConfigErrorType in GConf.idl, and some switch statements in the code */ typedef enum { /*< prefix=GCONF_ERROR >*/  GCONF_ERROR_SUCCESS = 0,  GCONF_ERROR_FAILED = 1,        /* Something didn't work, don't know why, probably unrecoverable  // … 参考gconf-error.h    GCONF_ERROR_IN_SHUTDOWN = 16   /* server is shutting down */ } GConfError;   2 定义与上面枚举对应的错误信息字符串数组: static const gchar* err_msgs[] = {  N_("Success"),  N_("Failed"),  // … 参考gconf-error.c  N_("No database available to save your configuration") }; 这点和GStreamer的差距:没有GSteamer做的巧妙     DBus的差距:不能像dbus那样灵活修改;   3 定义gconf的身份证,就是声明一个quark,GStreamer类似 #define GCONF_ERROR gconf_error_quark ()   GQuark gconf_error_quark (void) {  static GQuark err_q = 0;    if (err_q == 0)     err_q = g_quark_from_static_string ("gconf-error-quark");    return err_q; }     4 把错误信息填充到GError中   static GError* gconf_error_new_valist(GConfError en, const gchar* fmt, va_list args);   GError* gconf_error_new(GConfError en, const gchar* fmt, ...);   void gconf_set_error      (GError** err, GConfError en, const gchar* fmt, ...);   // … 具体参考gconf-error.c 从上面可以看出,GConf的error是委身于GError的,没有独立性。   5 外部调用GConf的接口API,大部分接口API都有一个GError** err参数,就是用这个参数接收Gconf返回的错误信息的;   C) gstreamer的做法   ==================Gstreamer error code 做法=============   Gstreamer中如何做error code/error string的? GStreamer中有4大类error: 1 GST_CORE_ERROR 2 GST_LIBRARY_ERROR 3 GST_RESOURCE_ERROR 4 GST_STREAM_ERROR   每一大类都有一些细分的error:   1, coreerror,主要是gstreamer核心概念方面的错误 typedef enum {  GST_CORE_ERROR_FAILED = 1,  GST_CORE_ERROR_TOO_LAZY,  GST_CORE_ERROR_NOT_IMPLEMENTED,  GST_CORE_ERROR_STATE_CHANGE,  GST_CORE_ERROR_PAD,  GST_CORE_ERROR_THREAD,  GST_CORE_ERROR_NEGOTIATION,  GST_CORE_ERROR_EVENT,  GST_CORE_ERROR_SEEK,  GST_CORE_ERROR_CAPS,  GST_CORE_ERROR_TAG,  GST_CORE_ERROR_MISSING_PLUGIN,  GST_CORE_ERROR_CLOCK,  GST_CORE_ERROR_NUM_ERRORS } GstCoreError;   2, 库操作方面的error typedef enum {  GST_LIBRARY_ERROR_FAILED = 1,  GST_LIBRARY_ERROR_TOO_LAZY,  GST_LIBRARY_ERROR_INIT,  GST_LIBRARY_ERROR_SHUTDOWN,  GST_LIBRARY_ERROR_SETTINGS,  GST_LIBRARY_ERROR_ENCODE,  GST_LIBRARY_ERROR_NUM_ERRORS } GstLibraryError;   3,资源方面的error   typedef enum {  GST_RESOURCE_ERROR_FAILED = 1,  GST_RESOURCE_ERROR_TOO_LAZY,  GST_RESOURCE_ERROR_NOT_FOUND,  GST_RESOURCE_ERROR_BUSY,  GST_RESOURCE_ERROR_OPEN_READ,  GST_RESOURCE_ERROR_OPEN_WRITE,  GST_RESOURCE_ERROR_OPEN_READ_WRITE,  GST_RESOURCE_ERROR_CLOSE,  GST_RESOURCE_ERROR_READ,  GST_RESOURCE_ERROR_WRITE,  GST_RESOURCE_ERROR_SEEK,  GST_RESOURCE_ERROR_SYNC,  GST_RESOURCE_ERROR_SETTINGS,  GST_RESOURCE_ERROR_NO_SPACE_LEFT,  GST_RESOURCE_ERROR_NUM_ERRORS } GstResourceError;   4 编解码方面的error   typedef enum {  GST_STREAM_ERROR_FAILED = 1,  GST_STREAM_ERROR_TOO_LAZY,  GST_STREAM_ERROR_NOT_IMPLEMENTED,  GST_STREAM_ERROR_TYPE_NOT_FOUND,  GST_STREAM_ERROR_WRONG_TYPE,  GST_STREAM_ERROR_CODEC_NOT_FOUND,  GST_STREAM_ERROR_DECODE,  GST_STREAM_ERROR_ENCODE,  GST_STREAM_ERROR_DEMUX,  GST_STREAM_ERROR_MUX,  GST_STREAM_ERROR_FORMAT,  GST_STREAM_ERROR_NUM_ERRORS } GstStreamError;   上面定义这么多的枚举量,很多人会直接去返回这个错误,没错,可以这么做。但是不直观,不能充分表达错误的原因! 为了充分把错误的原因传达给调用者,GStreamer把上面的error枚举都做了一个对应的字符串,作的比较巧妙。   做法如下:   1 定义一个quark #define QUARK_FUNC(string) / GQuark gst_ ## string ## _error_quark (void) {  /  static GQuark quark;   /  if (!quark)  /     quark = g_quark_from_static_string ("gst-" # string "-error-quark"); /  return quark; }   大家知道:夸克是比原子还小的单位。在glib里面quark是用来区分唯一性的; 一般是根据一个字符串得出一个唯一的quark, 这个产出的quark就用来唯一标识那个字符串;   2 为每种error颁发一个身份证明: QUARK_FUNC (core); QUARK_FUNC (library); QUARK_FUNC (resource); QUARK_FUNC (stream); 上面这4句话就是4个函数的implementation了:   GQuark gst_core_error_quark (void){ //…} GQuark gst_library_error_quark (void) { //…} GQuark gst_ resource _error_quark (void) { //…} GQuark gst_stream_error_quark (void) { //…}   //为了方便起见,再定义二代身份证 #define GST_LIBRARY_ERROR   gst_library_error_quark () #define GST_RESOURCE_ERROR gst_resource_error_quark () #define GST_CORE_ERROR      gst_core_error_quark () #define GST_STREAM_ERROR    gst_stream_error_quark () 有了表示4error的身份证了。     3 定义一个做字符串数组的宏   #define TABLE(t, d, a, b) t[GST_ ## d ## _ERROR_ ## a] = g_strdup (b) 这里面 t 是字符串数组的名字; d 是domain,上述枚举量的中间部分(蓝 {MOD}标出); a 是上述枚举量的后面半截(红 {MOD}标出); b 是错误字符串,这里可以给出详细的信息;   4 把上面定义的4类枚举变量对应的错误信息做4个字符串数组   4-1: core方面的详细错误信息     static gchar ** _gst_core_errors_init (void) {  gchar **t = NULL;    t = g_new0 (gchar *, GST_CORE_ERROR_NUM_ERRORS);    TABLE (t, CORE, FAILED,       N_("GStreamer encountered a general core library error."));  TABLE (t, CORE, TOO_LAZY,       N_("GStreamer developers were too lazy to assign an error code "           "to this error." FILE_A_BUG));  TABLE (t, CORE, NOT_IMPLEMENTED,       N_("Internal GStreamer error: code not implemented." FILE_A_BUG));  TABLE (t, CORE, STATE_CHANGE,       N_("Internal GStreamer error: state change failed." FILE_A_BUG));  TABLE (t, CORE, PAD, N_("Internal GStreamer error: pad problem." FILE_A_BUG));  TABLE (t, CORE, THREAD,       N_("Internal GStreamer error: thread problem." FILE_A_BUG));  TABLE (t, CORE, NEGOTIATION,       N_("Internal GStreamer error: negotiation problem." FILE_A_BUG));  TABLE (t, CORE, EVENT,       N_("Internal GStreamer error: event problem." FILE_A_BUG));  TABLE (t, CORE, SEEK,       N_("Internal GStreamer error: seek problem." FILE_A_BUG));  TABLE (t, CORE, CAPS,       N_("Internal GStreamer error: caps problem." FILE_A_BUG));  TABLE (t, CORE, TAG, N_("Internal GStreamer error: tag problem." FILE_A_BUG));  TABLE (t, CORE, MISSING_PLUGIN,       N_("Your GStreamer installation is missing a plug-in."));  TABLE (t, CORE, CLOCK,       N_("Internal GStreamer error: clock problem." FILE_A_BUG));    return t; }   //上面的函数的执行后的结果 gchar **gst_core_errors = _gst_core_errors_init ();   gst_core_errors [GST_CORE_ERROR_FAILED] = "GStreamer encountered a general core library error." gst_core_errors [GST_CORE_ERROR_TOO_LAZY] = "GStreamer developers were too lazy to assign an error code to this error."   //…   这样,用户就可以根据error code得出对应的error string了。     static gchar ** _gst_library_errors_init (void) {  // …. 省略  return t; }   /* initialize the dynamic table of translated resource errors */ static gchar ** _gst_resource_errors_init (void) {  // …. 省略 }   static gchar ** _gst_stream_errors_init (void) {  // …. 省略 }   具体可以参考gsterror.h/.c   这样就有了4个字符串数组。怎么来区分呢,就是用quark和error code:   gchar * gst_error_get_message (GQuark domain, gint code) {  static gchar **gst_core_errors = NULL;  static gchar **gst_library_errors = NULL;  static gchar **gst_resource_errors = NULL;  static gchar **gst_stream_errors = NULL;    gchar *message = NULL;    /* initialize error message tables if necessary */  if (gst_core_errors == NULL)     gst_core_errors = _gst_core_errors_init ();  if (gst_library_errors == NULL)     gst_library_errors = _gst_library_errors_init ();  if (gst_resource_errors == NULL)     gst_resource_errors = _gst_resource_errors_init ();  if (gst_stream_errors == NULL)     gst_stream_errors = _gst_stream_errors_init ();    // 根据身份证error code , 提取error string.  if (domain == GST_CORE_ERROR)     message = gst_core_errors[code];  else if (domain == GST_LIBRARY_ERROR)     message = gst_library_errors[code];  else if (domain == GST_RESOURCE_ERROR)     message = gst_resource_errors[code];  else if (domain == GST_STREAM_ERROR)     message = gst_stream_errors[code];  else {     g_warning ("No error messages for domain %s", g_quark_to_string (domain));     return g_strdup_printf (_("No error message for domain %s."),         g_quark_to_string (domain));  }  if (message)     return g_strdup (_(message));  else     return         g_strdup_printf (_         ("No standard error message for domain %s and code %d."),         g_quark_to_string (domain), code); }     如果开发middleware的话,GStreamer的error code的做法就非常值得参考;如果做application可以直接return error code, 或者也这么做,好像application很少有人这么干过。     D) dbus的做法   ==================dbus error code 做法=============   Dbuserror code的做法有另外一种风格,可以接收可变参数   1 定义一个数据结构DBusError   typedef struct DBusError DBusError;   /**  * Object representing an exception.  */ struct DBusError {  const char *name;    /**< error name */ // 主要的  const char *message; /**< error message */ // 主要的    unsigned int dummy1 : 1; /**< placeholder */  unsigned int dummy2 : 1; /**< placeholder */  unsigned int dummy3 : 1; /**< placeholder */  unsigned int dummy4 : 1; /**< placeholder */  unsigned int dummy5 : 1; /**< placeholder */    void *padding1; /**< placeholder */ };   2 然后dbus提供几个函数对这个数据结构进行分配内存、赋值、释放等的操作;   // 分配内存 void       dbus_error_init      (DBusError       *error);   // 释放 void        dbus_error_free      (DBusError       *error);   // 这是dbus的特 {MOD}菜肴,能灵活的对DBusError填充想填充的内容,不必事先定死 void        dbus_set_error       (DBusError       *error,                                   const char      *name,                                   const char      *message,                                   ...); // 这个是上面那个函数的简化版本 void        dbus_set_error_const (DBusError       *error,                                   const char      *name,                                   const char      *message);   void        dbus_move_error      (DBusError       *src,                                   DBusError       *dest); // 查询之用 dbus_bool_t dbus_error_has_name (const DBusError *error,                                   const char      *name); dbus_bool_t dbus_error_is_set    (const DBusError *error);   具体实现,大家可以参考dbus-error.c   如何使用上面的Error呢?分为外部和内部使用 1先看dbus内部如何使用的: 1-1 定义一些常见的error string, 这是准备填充到DBusError name字段上去的;   #define DBUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" #define DBUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" //……., 参考dbus-protocol.h #define DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"   1-2 初始化一个DBusError,并且填充错误信息给它   DBusError error; dbus_error_init (&error);   比如说 dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);   或者://这个就是往DBusErrormessage字段填充可变参数,这样信息可以灵活点         dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED,                         "Process %s received signal %d",                         sitter->executable, WTERMSIG (sitter->status));   2 外部如果调用dbus的API,并且该API有个参数DBusError *error的话,可以这样调用   //同样也是初始化一个error DBusError error; dbus_error_init (&error);   //然后调用dbus API If (!dbus_message_get_args (msg, &error, …) {     printf (“Can’t get arguments: %s”, error.message);     dbus_error_free (&error); //别忘记释放 }   大家可以看到,外部调用非常简单,其实我在这里聊dbus的error code,主要是想借鉴dbus内部调用的方式,以及DBusError的实现方式; 我们知道,dbus一般是作为middleware一层的代表,这种灵活多变的error code的做法也是继承自Glib的。在middleware一层,可以多考虑这种做法,比较专业,能传递更多的信息。 总结:   比较而言: 1 GconfError就是GError的升级版本; 2 GStreamer的Error分类思想很好,特别对于提供复杂功能的middleware,可以借鉴; 3 DBusError的可变填充比较好; 4 Sqlite 完全就是个一对一的关系,比较有特 {MOD}的就是可以提取“最新”的错误,很有用;