DRM后端Cairo-perf-trace工具崩溃问题
by Jiang Biao
为了测试Cairo DRM Backend的性能,重新编译cairo,编译时着实折腾了一把,但编出来后Cairo-perf-trace工具测试时崩溃,出现段错误,更是让人崩溃。
traces数据获取
从如下git仓库中下载的traces数据:
https://github.com/ssvb/trimmed-cairo-traces
这个仓库中的traces数据相比cairo官方的traces,进行了裁剪,测试时间更短,官方的数据太大,暂未使用,官方git如下:
http://cgit.freedesktop.org/cairo-traces
问题现象
Cairo-perf-trace工具编译完成后,执行benchmark中的gnome-terminal-vim的测试,执行到DRM后端时出现了段错误,从屏幕上打印看是在_cairo_format_from_content接口中出现了断言错误。
从生成的core文件看,现象如下:
Using host libthread_db library "/lib64/libthread_db.so.1".
Core was generated by `perf/.libs/cairo-perf-trace trimmed-cairo-traces-master/benchmark/t-gnome-termi'.
Program terminated with signal SIGABRT, Aborted.
\#0 0x000000fff1e3bbcc in raise () from /lib64/libc.so.6
...
(gdb) bt
#0 0x000000fff1e3bbcc in raise () from /lib64/libc.so.6
#1 0x000000fff1e3ddc0 in abort () from /lib64/libc.so.6
#2 0x000000fff1e33340 in __assert_fail_base () from /lib64/libc.so.6
#3 0x000000fff1e3340c in __assert_fail () from /lib64/libc.so.6
#4 0x000000fff273d7c0 in radeon_surface_create_similar () from /lib64/libcairo.so.2
#5 0x0000000120007228 in fill_surface (surface=<optimized out>) at cairo-perf-trace.c:176
#6 cairo_perf_trace.lto_priv.89 (perf=0xfffff0b240, target=0x120024670 <targets.lto_priv>, trace=0xfffff0f409 "trimmed-cairo-traces-master/benchmark/t-gnome-terminal-vim")
at cairo-perf-trace.c:719
#7 0x0000000120004df4 in main (argc=<optimized out>, argv=<optimized out>) at cairo-perf-trace.c:1032
从堆栈上看,应该是在radeon_surface_create_similar()
函数中出现了断言错误。
问题分析
分析堆栈
从错误堆栈出发,从故障现场出发,分析radeon_surface_create_similar()
函数代码其中调用了_cairo_format_from_content()
函数,该函数中有如下断言:
_cairo_format_from_content (cairo_content_t content)
{
switch (content) {
case CAIRO_CONTENT_COLOR:
return CAIRO_FORMAT_RGB24;
case CAIRO_CONTENT_ALPHA:
return CAIRO_FORMAT_A8;
case CAIRO_CONTENT_COLOR_ALPHA:
return CAIRO_FORMAT_ARGB32;
}
ASSERT_NOT_REACHED;
return CAIRO_FORMAT_INVALID;
}
流程走到了ASSERT_NOT_REACHED,说明conent的内容不对。接下来需要确认content的数据来源。
从堆栈上看content被优化掉了,而且出现断言后,通过raise()
抛出异常,故障现场已经走过了,很多信息已无法确认,无法确认content的内容。只能分析代码了。
代码分析
分析堆栈中的流程,radeon_surface_create_similar()
是从fill_surface()
进入,分析fill_surface()
函数代码:
static void
fill_surface (cairo_surface_t *surface)
{
cairo_t *cr = cairo_create (surface);
/* This needs to be an operation that the backends can't optimise away */
cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_paint (cr);
cairo_destroy (cr);
}
只能是从cairo_create()
进入的了:
cairo_t *
cairo_create (cairo_surface_t *target)
{
if (unlikely (target == NULL))
return _cairo_create_in_error (_cairo_error (CAIRO_STATUS_NULL_POINTER));
if (unlikely (target->status))
return _cairo_create_in_error (target->status);
if (target->backend->create_context == NULL)
return _cairo_create_in_error (_cairo_error (CAIRO_STATUS_WRITE_ERROR));
return target->backend->create_context (target);
}
继续跟踪 target->backend->create_context (target)
,是调用了指定后端的create_context()
钩子。
根据执行流程分析,当前应该正在执行drm后端的相关操作,看看此时drm后端对应的backend应该是:radeon_surface_backend
,看看其初始化:
static const cairo_surface_backend_t radeon_surface_backend = {
CAIRO_SURFACE_TYPE_DRM,
_cairo_default_context_create,
radeon_surface_create_similar,
radeon_surface_finish,
NULL,
radeon_surface_acquire_source_image,
radeon_surface_release_source_image,
NULL, NULL, NULL,
NULL, /* composite */
NULL, /* fill */
NULL, /* trapezoids */
NULL, /* span */
NULL, /* check-span */
NULL, /* copy_page */
NULL, /* show_page */
_cairo_drm_surface_get_extents,
NULL, /* old-glyphs */
_cairo_drm_surface_get_font_options,
radeon_surface_flush,
NULL, /* mark dirty */
NULL, NULL, /* font/glyph fini */
radeon_surface_paint,
radeon_surface_mask,
radeon_surface_stroke,
radeon_surface_fill,
radeon_surface_glyphs,
};
看起来像是调用了_cairo_default_context_create
接口,继续跟踪该接口发现无论如何也不可能走到radeon_surface_create_similar()
流程,毕竟,从函数作用的理解看,context_create()
接口是创建cairo_t上下文的,而surface_create_similar()
接口则是创建surface的,作用并不相同,理论上,应该需要先创建surface,然后通过已经创建好的surface来创建context。
是哪里出了问题,是堆栈不准导致的么?回头再仔细看看radeon_surface_backend
的定义才发现了问题的根源:radeon_surface_backend
中的钩子挂错位了。
radeon_surface_backend
是cairo_surface_backend_t
类型,看看cairo_surface_backend_t
的定义:
struct _cairo_surface_backend {
cairo_surface_type_t type;
cairo_warn cairo_status_t
(*finish) (void *surface);
cairo_t *
(*create_context) (void *surface);
cairo_surface_t *
(*create_similar) (void *surface,
cairo_content_t content,
int width,
int height);
cairo_surface_t *
(*create_similar_image) (void *surface,
cairo_format_t format,
int width,
int height);
...
发现create_context
钩子在结构体的第3个字段,而radeon_surface_backend
初始化是第3个字段赋值为radeon_surface_create_similar()
,正确的应该是_cairo_default_context_create
,应该是错位的,radeon_surface_backend
中应该吧radeon_surface_finish
放到第2个字段,如此对齐。
查看其他后端(如pdf)的定义,都是如此。
钩子错位后,导致调用函数出错,create_context
函数只有一个参数,而create_similar
函数有4个参数,当调用create_similar
函数时,由于只传入了1个实参,在取后面3个参数时,就可能取到随机值了,所以出现了段错误。修正后即可解决。
看看cairo中Drm后端中intel的backend的代码,也是这样写的,看起来intel的驱动也有相同的问题,但cairo(我用的是1.13版本)中drm后端的代码问题远不止于此,其中非常多的问题,尤其是Intel的驱动,很多代码甚至无法编译通过,问题相当多,可见Intel在合入代码时甚至没有做过自测,真是不得不感叹Intel专家们的责任心和编码水平。
瞅了眼最新的cairo代码,看似没有明显改善,可能Intel等厂商们并不看重cairo的DRM后端,这也许正是DRM后端一直还是experimental状态的主要原因。
这个问题修正后,cairo-perf-trace工具运行仍有段错误,估计还会有更多的问题,其他文章中再聊吧。
Subscribe via RSS