为了测试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_backendcairo_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工具运行仍有段错误,估计还会有更多的问题,其他文章中再聊吧。