diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fffae4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+SOURCES/satyr-0.13.tar.xz
diff --git a/.satyr.metadata b/.satyr.metadata
new file mode 100644
index 0000000..bdf2891
--- /dev/null
+++ b/.satyr.metadata
@@ -0,0 +1 @@
+3a1e70739102d91879ca625670e8efe60817b608 SOURCES/satyr-0.13.tar.xz
diff --git a/README.md b/README.md
deleted file mode 100644
index 0e7897f..0000000
--- a/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-The master branch has no content
- 
-Look at the c7 branch if you are working with CentOS-7, or the c4/c5/c6 branch for CentOS-4, 5 or 6
- 
-If you find this file in a distro specific branch, it means that no content has been checked in yet
diff --git a/SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch b/SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch
new file mode 100644
index 0000000..bd1c13e
--- /dev/null
+++ b/SOURCES/satyr-0.13-Add-support-for-Ruby-report-type.patch
@@ -0,0 +1,2586 @@
+From 28db4ce2840d74f06b55c1e2c7c48f228708fb1d Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 28 Jan 2015 16:11:15 +0100
+Subject: [PATCH] Add support for Ruby report type
+
+Related: #1334604
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ include/Makefile.am                          |   5 +
+ include/report_type.h                        |   1 +
+ include/ruby/frame.h                         |  99 ++++++
+ include/ruby/stacktrace.h                    |  73 ++++
+ lib/Makefile.am                              |   2 +
+ lib/abrt.c                                   |  31 ++
+ lib/generic_frame.c                          |   1 +
+ lib/generic_frame.h                          |   3 +-
+ lib/generic_stacktrace.c                     |   1 +
+ lib/generic_stacktrace.h                     |   3 +-
+ lib/generic_thread.c                         |   1 +
+ lib/generic_thread.h                         |   3 +-
+ lib/report.c                                 |   3 +
+ lib/ruby_frame.c                             | 504 +++++++++++++++++++++++++++
+ lib/ruby_stacktrace.c                        | 387 ++++++++++++++++++++
+ tests/Makefile.am                            |   3 +
+ tests/ruby_frame.at                          | 285 +++++++++++++++
+ tests/ruby_stacktrace.at                     | 316 +++++++++++++++++
+ tests/ruby_stacktraces/ruby-01               |  23 ++
+ tests/ruby_stacktraces/ruby-01-expected-json |  97 ++++++
+ tests/ruby_stacktraces/ruby-02               |  37 ++
+ tests/ruby_stacktraces/ruby-02-expected-json | 158 +++++++++
+ tests/ruby_stacktraces/ruby-03               |   4 +
+ tests/ruby_stacktraces/ruby-03-expected-json |  19 +
+ tests/ruby_stacktraces/ruby-04               |  39 +++
+ tests/ruby_stacktraces/ruby-04-expected-json | 166 +++++++++
+ tests/testsuite.at                           |   2 +
+ 27 files changed, 2263 insertions(+), 3 deletions(-)
+ create mode 100644 include/ruby/frame.h
+ create mode 100644 include/ruby/stacktrace.h
+ create mode 100644 lib/ruby_frame.c
+ create mode 100644 lib/ruby_stacktrace.c
+ create mode 100644 tests/ruby_frame.at
+ create mode 100644 tests/ruby_stacktrace.at
+ create mode 100644 tests/ruby_stacktraces/ruby-01
+ create mode 100644 tests/ruby_stacktraces/ruby-01-expected-json
+ create mode 100644 tests/ruby_stacktraces/ruby-02
+ create mode 100644 tests/ruby_stacktraces/ruby-02-expected-json
+ create mode 100644 tests/ruby_stacktraces/ruby-03
+ create mode 100644 tests/ruby_stacktraces/ruby-03-expected-json
+ create mode 100644 tests/ruby_stacktraces/ruby-04
+ create mode 100644 tests/ruby_stacktraces/ruby-04-expected-json
+
+diff --git a/include/Makefile.am b/include/Makefile.am
+index f72ec66..bcffe31 100644
+--- a/include/Makefile.am
++++ b/include/Makefile.am
+@@ -49,3 +49,8 @@ pythonheadersdir = $(includedir)/satyr/python
+ pythonheaders_HEADERS = \
+ 	python/frame.h \
+ 	python/stacktrace.h
++
++rubyheadersdir = $(includedir)/satyr/ruby
++rubyheaders_HEADERS = \
++	ruby/frame.h \
++	ruby/stacktrace.h
+diff --git a/include/report_type.h b/include/report_type.h
+index a51620d..0d411ee 100644
+--- a/include/report_type.h
++++ b/include/report_type.h
+@@ -32,6 +32,7 @@ enum sr_report_type
+     SR_REPORT_KERNELOOPS,
+     SR_REPORT_JAVA,
+     SR_REPORT_GDB,
++    SR_REPORT_RUBY,
+ 
+     /* Keep this the last entry. */
+     SR_REPORT_NUM
+diff --git a/include/ruby/frame.h b/include/ruby/frame.h
+new file mode 100644
+index 0000000..ffc3c67
+--- /dev/null
++++ b/include/ruby/frame.h
+@@ -0,0 +1,99 @@
++/*
++    ruby_frame.h
++
++    Copyright (C) 2015  ABRT Team
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_RUBY_FRAME_H
++#define SATYR_RUBY_FRAME_H
++
++/**
++ * @file
++ * @brief Ruby frame structure and related routines.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include "../report_type.h"
++#include <stdbool.h>
++#include <stdint.h>
++
++struct sr_location;
++struct sr_strbuf;
++struct sr_json_value;
++
++struct sr_ruby_frame
++{
++    enum sr_report_type type;
++
++    char *file_name;
++
++    uint32_t file_line;
++
++    bool special_function;
++
++    char *function_name;
++
++    uint32_t block_level;
++
++    uint32_t rescue_level;
++
++    struct sr_ruby_frame *next;
++};
++
++struct sr_ruby_frame *
++sr_ruby_frame_new();
++
++void
++sr_ruby_frame_init(struct sr_ruby_frame *frame);
++
++void
++sr_ruby_frame_free(struct sr_ruby_frame *frame);
++
++struct sr_ruby_frame *
++sr_ruby_frame_dup(struct sr_ruby_frame *frame, bool siblings);
++
++int
++sr_ruby_frame_cmp(struct sr_ruby_frame *frame1, struct sr_ruby_frame *frame2);
++
++int
++sr_ruby_frame_cmp_distance(struct sr_ruby_frame *frame1,
++                           struct sr_ruby_frame *frame2);
++
++struct sr_ruby_frame *
++sr_ruby_frame_append(struct sr_ruby_frame *dest,
++                     struct sr_ruby_frame *item);
++
++struct sr_ruby_frame *
++sr_ruby_frame_parse(const char **input, struct sr_location *location);
++
++char *
++sr_ruby_frame_to_json(struct sr_ruby_frame *frame);
++
++struct sr_ruby_frame *
++sr_ruby_frame_from_json(struct sr_json_value *root, char **error_message);
++
++void
++sr_ruby_frame_append_to_str(struct sr_ruby_frame *frame, struct sr_strbuf *dest);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/include/ruby/stacktrace.h b/include/ruby/stacktrace.h
+new file mode 100644
+index 0000000..caa3e0b
+--- /dev/null
++++ b/include/ruby/stacktrace.h
+@@ -0,0 +1,73 @@
++/*
++    ruby_stacktrace.h
++
++    Copyright (C) 2015  ABRT Team
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_RUBY_STACKTRACE_H
++#define SATYR_RUBY_STACKTRACE_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include "../report_type.h"
++#include <stdint.h>
++
++struct sr_ruby_frame;
++struct sr_location;
++struct sr_json_value;
++
++struct sr_ruby_stacktrace
++{
++    enum sr_report_type type;
++
++    char *exception_name;
++
++    struct sr_ruby_frame *frames;
++};
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_new();
++
++void
++sr_ruby_stacktrace_init(struct sr_ruby_stacktrace *stacktrace);
++
++void
++sr_ruby_stacktrace_free(struct sr_ruby_stacktrace *stacktrace);
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_dup(struct sr_ruby_stacktrace *stacktrace);
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_parse(const char **input,
++                         struct sr_location *location);
++
++char *
++sr_ruby_stacktrace_get_reason(struct sr_ruby_stacktrace *stacktrace);
++
++char *
++sr_ruby_stacktrace_to_json(struct sr_ruby_stacktrace *stacktrace);
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_from_json(struct sr_json_value *root, char **error_message);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index f798347..26016f2 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -57,6 +57,8 @@ libsatyr_conv_la_SOURCES = \
+ 	python_stacktrace.c \
+ 	report.c \
+ 	rpm.c \
++	ruby_frame.c \
++	ruby_stacktrace.c \
+ 	sha1.c \
+ 	strbuf.c \
+ 	unstrip.c \
+diff --git a/lib/abrt.c b/lib/abrt.c
+index 4599e2c..a07d44a 100644
+--- a/lib/abrt.c
++++ b/lib/abrt.c
+@@ -30,6 +30,7 @@
+ #include "python/stacktrace.h"
+ #include "koops/stacktrace.h"
+ #include "java/stacktrace.h"
++#include "ruby/stacktrace.h"
+ #include "json.h"
+ #include "location.h"
+ #include <stdio.h>
+@@ -567,6 +568,34 @@ sr_abrt_report_from_dir(const char *directory,
+         }
+     }
+ 
++    /* Ruby stacktrace. */
++    if (report->report_type == SR_REPORT_RUBY)
++    {
++        char *backtrace_contents = file_contents(directory, "backtrace",
++                                                 error_message);
++        if (!backtrace_contents)
++        {
++            sr_report_free(report);
++            return NULL;
++        }
++
++        /* Parse the Ruby stacktrace. */
++        struct sr_location location;
++        sr_location_init(&location);
++        const char *contents_pointer = backtrace_contents;
++        report->stacktrace = (struct sr_stacktrace *)sr_ruby_stacktrace_parse(
++            &contents_pointer,
++            &location);
++
++        free(backtrace_contents);
++        if (!report->stacktrace)
++        {
++            *error_message = sr_location_to_string(&location);
++            sr_report_free(report);
++            return NULL;
++        }
++    }
++
+     return report;
+ }
+ 
+@@ -581,6 +610,8 @@ sr_abrt_type_from_analyzer(const char *analyzer)
+         return SR_REPORT_KERNELOOPS;
+     else if (0 == strncmp(analyzer, "Java", 4))
+         return SR_REPORT_JAVA;
++    else if (0 == strncmp(analyzer, "Ruby", 4))
++        return SR_REPORT_RUBY;
+ 
+     return SR_REPORT_INVALID;
+ }
+diff --git a/lib/generic_frame.c b/lib/generic_frame.c
+index eb644b5..d5512c9 100644
+--- a/lib/generic_frame.c
++++ b/lib/generic_frame.c
+@@ -34,6 +34,7 @@ static struct frame_methods* dtable[SR_REPORT_NUM] =
+     [SR_REPORT_KERNELOOPS] = &koops_frame_methods,
+     [SR_REPORT_JAVA] = &java_frame_methods,
+     [SR_REPORT_GDB] = &gdb_frame_methods,
++    [SR_REPORT_RUBY] = &ruby_frame_methods,
+ };
+ 
+ void
+diff --git a/lib/generic_frame.h b/lib/generic_frame.h
+index a61626d..e9f392d 100644
+--- a/lib/generic_frame.h
++++ b/lib/generic_frame.h
+@@ -48,7 +48,8 @@ struct frame_methods
+ };
+ 
+ extern struct frame_methods core_frame_methods, python_frame_methods,
+-       koops_frame_methods, gdb_frame_methods, java_frame_methods;
++       koops_frame_methods, gdb_frame_methods, java_frame_methods,
++       ruby_frame_methods;
+ 
+ void
+ frame_append_bthash_text(struct sr_frame *frame, enum sr_bthash_flags flags,
+diff --git a/lib/generic_stacktrace.c b/lib/generic_stacktrace.c
+index feb1dbc..f98a482 100644
+--- a/lib/generic_stacktrace.c
++++ b/lib/generic_stacktrace.c
+@@ -40,6 +40,7 @@ static struct stacktrace_methods* dtable[SR_REPORT_NUM] =
+     [SR_REPORT_KERNELOOPS] = &koops_stacktrace_methods,
+     [SR_REPORT_JAVA] = &java_stacktrace_methods,
+     [SR_REPORT_GDB] = &gdb_stacktrace_methods,
++    [SR_REPORT_RUBY] = &ruby_stacktrace_methods,
+ };
+ 
+ /* In case stacktrace type supports only one thread, return the stacktrace itself. */
+diff --git a/lib/generic_stacktrace.h b/lib/generic_stacktrace.h
+index 8e3d629..6e1a444 100644
+--- a/lib/generic_stacktrace.h
++++ b/lib/generic_stacktrace.h
+@@ -55,7 +55,8 @@ struct stacktrace_methods
+ };
+ 
+ extern struct stacktrace_methods core_stacktrace_methods, python_stacktrace_methods,
+-       koops_stacktrace_methods, gdb_stacktrace_methods, java_stacktrace_methods;
++       koops_stacktrace_methods, gdb_stacktrace_methods, java_stacktrace_methods,
++       ruby_stacktrace_methods;
+ 
+ /* Macros to generate accessors for the "threads" member. */
+ #define DEFINE_THREADS_FUNC(name, concrete_t) \
+diff --git a/lib/generic_thread.c b/lib/generic_thread.c
+index 59fff1b..16748b5 100644
+--- a/lib/generic_thread.c
++++ b/lib/generic_thread.c
+@@ -131,6 +131,7 @@ static struct thread_methods* dtable[SR_REPORT_NUM] =
+     [SR_REPORT_KERNELOOPS] = &koops_thread_methods,
+     [SR_REPORT_JAVA] = &java_thread_methods,
+     [SR_REPORT_GDB] = &gdb_thread_methods,
++    [SR_REPORT_RUBY] = &ruby_thread_methods,
+ };
+ 
+ struct sr_frame *
+diff --git a/lib/generic_thread.h b/lib/generic_thread.h
+index 005e5ac..8635ad2 100644
+--- a/lib/generic_thread.h
++++ b/lib/generic_thread.h
+@@ -56,7 +56,8 @@ struct thread_methods
+ };
+ 
+ extern struct thread_methods core_thread_methods, python_thread_methods,
+-       koops_thread_methods, gdb_thread_methods, java_thread_methods;
++       koops_thread_methods, gdb_thread_methods, java_thread_methods,
++       ruby_thread_methods;
+ 
+ /* Macros to generate accessors for the "frames" member. */
+ #define DEFINE_FRAMES_FUNC(name, concrete_t) \
+diff --git a/lib/report.c b/lib/report.c
+index 411df64..614b213 100644
+--- a/lib/report.c
++++ b/lib/report.c
+@@ -42,6 +42,7 @@ static char *report_types[] =
+     [SR_REPORT_KERNELOOPS] = "kerneloops",
+     [SR_REPORT_JAVA] = "java",
+     [SR_REPORT_GDB] = "gdb",
++    [SR_REPORT_RUBY] = "ruby",
+     NULL
+ };
+ 
+@@ -189,6 +190,7 @@ sr_report_to_json(struct sr_report *report)
+     case SR_REPORT_PYTHON:
+     case SR_REPORT_KERNELOOPS:
+     case SR_REPORT_JAVA:
++    case SR_REPORT_RUBY:
+         report_type = sr_report_type_to_string(report->report_type);
+         reason = sr_stacktrace_get_reason(report->stacktrace);
+         break;
+@@ -383,6 +385,7 @@ sr_report_from_json(struct sr_json_value *root, char **error_message)
+         case SR_REPORT_PYTHON:
+         case SR_REPORT_KERNELOOPS:
+         case SR_REPORT_JAVA:
++        case SR_REPORT_RUBY:
+             report->stacktrace = sr_stacktrace_from_json(report->report_type, problem, error_message);
+             break;
+         default:
+diff --git a/lib/ruby_frame.c b/lib/ruby_frame.c
+new file mode 100644
+index 0000000..9a9a317
+--- /dev/null
++++ b/lib/ruby_frame.c
+@@ -0,0 +1,504 @@
++/*
++    ruby_frame.c
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include "strbuf.h"
++#include "json.h"
++#include "generic_frame.h"
++#include "thread.h"
++#include "stacktrace.h"
++#include "internal_utils.h"
++#include <string.h>
++#include <inttypes.h>
++#include <ctype.h>
++
++/* Method table */
++
++static void
++ruby_append_bthash_text(struct sr_ruby_frame *frame, enum sr_bthash_flags flags,
++                          struct sr_strbuf *strbuf);
++static void
++ruby_append_duphash_text(struct sr_ruby_frame *frame, enum sr_duphash_flags flags,
++                           struct sr_strbuf *strbuf);
++
++DEFINE_NEXT_FUNC(ruby_next, struct sr_frame, struct sr_ruby_frame)
++DEFINE_SET_NEXT_FUNC(ruby_set_next, struct sr_frame, struct sr_ruby_frame)
++
++struct frame_methods ruby_frame_methods =
++{
++    .append_to_str = (append_to_str_fn_t) sr_ruby_frame_append_to_str,
++    .next = (next_frame_fn_t) ruby_next,
++    .set_next = (set_next_frame_fn_t) ruby_set_next,
++    .cmp = (frame_cmp_fn_t) sr_ruby_frame_cmp,
++    .cmp_distance = (frame_cmp_fn_t) sr_ruby_frame_cmp_distance,
++    .frame_append_bthash_text =
++        (frame_append_bthash_text_fn_t) ruby_append_bthash_text,
++    .frame_append_duphash_text =
++        (frame_append_duphash_text_fn_t) ruby_append_duphash_text,
++    .frame_free = (frame_free_fn_t) sr_ruby_frame_free,
++};
++
++/* Public functions */
++
++struct sr_ruby_frame *
++sr_ruby_frame_new()
++{
++    struct sr_ruby_frame *frame =
++        sr_malloc(sizeof(struct sr_ruby_frame));
++
++    sr_ruby_frame_init(frame);
++    return frame;
++}
++
++void
++sr_ruby_frame_init(struct sr_ruby_frame *frame)
++{
++    memset(frame, 0, sizeof(struct sr_ruby_frame));
++    frame->type = SR_REPORT_RUBY;
++}
++
++void
++sr_ruby_frame_free(struct sr_ruby_frame *frame)
++{
++    if (!frame)
++        return;
++
++    free(frame->file_name);
++    free(frame->function_name);
++    free(frame);
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_dup(struct sr_ruby_frame *frame, bool siblings)
++{
++    struct sr_ruby_frame *result = sr_ruby_frame_new();
++    memcpy(result, frame, sizeof(struct sr_ruby_frame));
++
++    /* Handle siblings. */
++    if (siblings)
++    {
++        if (result->next)
++            result->next = sr_ruby_frame_dup(result->next, true);
++    }
++    else
++        result->next = NULL; /* Do not copy that. */
++
++    /* Duplicate all strings. */
++    if (result->file_name)
++        result->file_name = sr_strdup(result->file_name);
++
++    if (result->function_name)
++        result->function_name = sr_strdup(result->function_name);
++
++    return result;
++}
++
++int
++sr_ruby_frame_cmp(struct sr_ruby_frame *frame1,
++                  struct sr_ruby_frame *frame2)
++{
++    /* function_name */
++    int function_name = sr_strcmp0(frame1->function_name,
++                                   frame2->function_name);
++    if (function_name != 0)
++        return function_name;
++
++    /* file_name */
++    int file_name = sr_strcmp0(frame1->file_name,
++                               frame2->file_name);
++    if (file_name != 0)
++        return file_name;
++
++    /* file_line */
++    int file_line = frame1->file_line - frame2->file_line;
++    if (file_line != 0)
++        return file_line;
++
++    /* special_function */
++    int special_function = frame1->special_function - frame2->special_function;
++    if (special_function != 0)
++        return special_function;
++
++    /* block_level */
++    int block_level = frame1->block_level - frame2->block_level;
++    if (block_level != 0)
++            return block_level;
++
++    /* rescue_level */
++    int rescue_level = frame1->rescue_level - frame2->rescue_level;
++    if (rescue_level != 0)
++            return rescue_level;
++
++    return 0;
++}
++
++int
++sr_ruby_frame_cmp_distance(struct sr_ruby_frame *frame1,
++                           struct sr_ruby_frame *frame2)
++{
++    /* function_name */
++    int function_name = sr_strcmp0(frame1->function_name,
++                                   frame2->function_name);
++    if (function_name != 0)
++        return function_name;
++
++    /* file_name */
++    int file_name = sr_strcmp0(frame1->file_name,
++                               frame2->file_name);
++    if (file_name != 0)
++        return file_name;
++
++    /* special_function */
++    int special_function = frame1->special_function - frame2->special_function;
++    if (special_function != 0)
++        return special_function;
++
++    return 0;
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_append(struct sr_ruby_frame *dest,
++                     struct sr_ruby_frame *item)
++{
++    if (!dest)
++        return item;
++
++    struct sr_ruby_frame *dest_loop = dest;
++    while (dest_loop->next)
++        dest_loop = dest_loop->next;
++
++    dest_loop->next = item;
++    return dest;
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_parse(const char **input,
++                    struct sr_location *location)
++{
++    const char *local_input = *input;
++    struct sr_ruby_frame *frame = sr_ruby_frame_new();
++
++    /* take everything before the backtick
++     * /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++     */
++    char *filename_lineno_in = NULL;
++    if (!sr_parse_char_cspan(&local_input, "`", &filename_lineno_in))
++    {
++        location->message = sr_strdup("Unable to find the '`' character "
++                                      "identifying the beginning of function name.");
++        goto fail;
++    }
++
++    size_t l = strlen(filename_lineno_in);
++    location->column += l;
++
++    char *p = filename_lineno_in + l;
++
++    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     *                                                           ^^^^
++     */
++    p -= strlen(":in ");
++    if (p < filename_lineno_in || 0 != strcmp(":in ", p))
++    {
++        location->column -= strlen(":in ");
++        location->message = sr_strdup("Unable to find ':in ' preceding the "
++                                      "backtick character.");
++        goto fail;
++    }
++
++    /* Find the beginning of the line number. */
++    *p = '\0';
++    do {
++        p--;
++    } while (p >= filename_lineno_in && isdigit(*p));
++    p++;
++
++    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     *                                                         ^^
++     */
++    char *p_copy = p;
++    int lineno_len = sr_parse_uint32((const char **)&p_copy, &frame->file_line);
++    if (lineno_len <= 0)
++    {
++        location->message = sr_strdup("Unable to find line number before ':in '");
++        goto fail;
++    }
++
++    /* /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++     *                                                        ^
++     */
++    p--;
++    if (p < filename_lineno_in || *p != ':')
++    {
++        location->column -= lineno_len;
++        location->message = sr_strdup("Unable to fin the ':' character "
++                                      "preceding the line number");
++        goto fail;
++    }
++
++    /* Everything before the colon is the file name. */
++    *p = '\0';
++    frame->file_name = filename_lineno_in;
++    filename_lineno_in = NULL;
++
++    if(!sr_skip_char(&local_input, '`'))
++    {
++        location->message = sr_strdup("Unable to find the '`' character "
++                                      "identifying the beginning of function name.");
++        goto fail;
++    }
++
++    location->column++;
++
++    /* The part in quotes can look like:
++     * `rescue in rescue in block (3 levels) in func'
++     * parse the number of rescues and blocks
++     */
++    while (sr_skip_string(&local_input, "rescue in "))
++    {
++        frame->rescue_level++;
++        location->column += strlen("rescue in ");
++    }
++
++    if (sr_skip_string(&local_input, "block in "))
++    {
++        frame->block_level = 1;
++        location->column += strlen("block in");
++    }
++    else if(sr_skip_string(&local_input, "block ("))
++    {
++        location->column += strlen("block (");
++
++        int len = sr_parse_uint32(&local_input, &frame->block_level);
++        if (len == 0 || !sr_skip_string(&local_input, " levels) in "))
++        {
++            location->message = sr_strdup("Unable to parse block depth.");
++            goto fail;
++        }
++        location->column += len + strlen(" levels) in ");
++    }
++
++    if (sr_skip_char(&local_input, '<'))
++    {
++        location->column++;
++        frame->special_function = true;
++    }
++
++    if (!sr_parse_char_cspan(&local_input, "'>", &frame->function_name))
++    {
++        location->message = sr_strdup("Unable to find the \"'\" character "
++                                      "delimiting the function name.");
++        goto fail;
++    }
++    location->column += strlen(frame->function_name);
++
++    if (frame->special_function)
++    {
++        if (!sr_skip_char(&local_input, '>'))
++        {
++            location->message = sr_strdup("Unable to find the \">\" character "
++                                          "delimiting the function name.");
++            goto fail;
++        }
++        location->column++;
++    }
++
++    if (!sr_skip_char(&local_input, '\''))
++    {
++        location->message = sr_strdup("Unable to find the \"'\" character "
++                                      "delimiting the function name.");
++        goto fail;
++    }
++    location->column++;
++
++    *input = local_input;
++    return frame;
++
++fail:
++    sr_ruby_frame_free(frame);
++    free(filename_lineno_in);
++    return NULL;
++}
++
++char *
++sr_ruby_frame_to_json(struct sr_ruby_frame *frame)
++{
++    struct sr_strbuf *strbuf = sr_strbuf_new();
++
++    /* Source file name. */
++    if (frame->file_name)
++    {
++        sr_strbuf_append_str(strbuf, ",   \"file_name\": ");
++        sr_json_append_escaped(strbuf, frame->file_name);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
++    /* Source file line. */
++    if (frame->file_line)
++    {
++        sr_strbuf_append_strf(strbuf,
++                              ",   \"file_line\": %"PRIu32"\n",
++                              frame->file_line);
++    }
++
++    /* Function name / special function. */
++    if (frame->function_name)
++    {
++        if (frame->special_function)
++            sr_strbuf_append_str(strbuf, ",   \"special_function\": ");
++        else
++            sr_strbuf_append_str(strbuf, ",   \"function_name\": ");
++
++        sr_json_append_escaped(strbuf, frame->function_name);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
++    /* Block level. */
++    if (frame->block_level > 0)
++    {
++        sr_strbuf_append_strf(strbuf,
++                              ",   \"block_level\": %"PRIu32"\n",
++                              frame->block_level);
++    }
++
++    /* Rescue level. */
++    if (frame->rescue_level > 0)
++    {
++        sr_strbuf_append_strf(strbuf,
++                              ",   \"rescue_level\": %"PRIu32"\n",
++                              frame->rescue_level);
++    }
++
++
++    strbuf->buf[0] = '{';
++    sr_strbuf_append_char(strbuf, '}');
++    return sr_strbuf_free_nobuf(strbuf);
++}
++
++struct sr_ruby_frame *
++sr_ruby_frame_from_json(struct sr_json_value *root, char **error_message)
++{
++    if (!JSON_CHECK_TYPE(root, SR_JSON_OBJECT, "frame"))
++        return NULL;
++
++    struct sr_ruby_frame *result = sr_ruby_frame_new();
++    struct sr_json_value *val;
++
++    /* Source file name */
++    if ((val = json_element(root, "file_name")))
++    {
++        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "file_name"))
++            goto fail;
++
++        result->file_name = sr_strdup(val->u.string.ptr);
++    }
++
++    /* Function name / special function. */
++    if ((val = json_element(root, "function_name")))
++    {
++        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "function_name"))
++            goto fail;
++
++        result->special_function = false;
++        result->function_name = sr_strdup(val->u.string.ptr);
++    }
++    else if ((val = json_element(root, "special_function")))
++    {
++        if (!JSON_CHECK_TYPE(val, SR_JSON_STRING, "special_function"))
++            goto fail;
++
++        result->special_function = true;
++        result->function_name = sr_strdup(val->u.string.ptr);
++    }
++
++    bool success =
++        JSON_READ_UINT32(root, "file_line", &result->file_line) &&
++        JSON_READ_UINT32(root, "block_level", &result->block_level) &&
++        JSON_READ_UINT32(root, "rescue_level", &result->rescue_level);
++
++    if (!success)
++        goto fail;
++
++    return result;
++
++fail:
++    sr_ruby_frame_free(result);
++    return NULL;
++}
++
++void
++sr_ruby_frame_append_to_str(struct sr_ruby_frame *frame,
++                            struct sr_strbuf *dest)
++{
++    for (int i = 0; i < frame->rescue_level; i++)
++    {
++        sr_strbuf_append_str(dest, "rescue in ");
++    }
++
++    if (frame->block_level == 1)
++    {
++        sr_strbuf_append_str(dest, "block in ");
++    }
++    else if (frame->block_level > 1)
++    {
++        sr_strbuf_append_strf(dest, "block (%u levels) in ", (unsigned)frame->block_level);
++    }
++
++    sr_strbuf_append_strf(dest, "%s%s%s",
++                          (frame->special_function ? "<" : ""),
++                          (frame->function_name ? frame->function_name : "??"),
++                          (frame->special_function ? ">" : ""));
++
++    if (frame->file_name)
++    {
++        sr_strbuf_append_strf(dest, " in %s", frame->file_name);
++
++        if (frame->file_line)
++        {
++            sr_strbuf_append_strf(dest, ":%d", frame->file_line);
++        }
++    }
++}
++
++static void
++ruby_append_bthash_text(struct sr_ruby_frame *frame, enum sr_bthash_flags flags,
++                        struct sr_strbuf *strbuf)
++{
++    sr_strbuf_append_strf(strbuf,
++                          "%s, %"PRIu32", %s, %d, %"PRIu32", %"PRIu32"\n",
++                          OR_UNKNOWN(frame->file_name),
++                          frame->file_line,
++                          OR_UNKNOWN(frame->function_name),
++                          frame->special_function,
++                          frame->block_level,
++                          frame->rescue_level);
++}
++
++static void
++ruby_append_duphash_text(struct sr_ruby_frame *frame, enum sr_duphash_flags flags,
++                         struct sr_strbuf *strbuf)
++{
++    /* filename:line */
++    sr_strbuf_append_strf(strbuf, "%s:%"PRIu32"\n",
++                          OR_UNKNOWN(frame->file_name),
++                          frame->file_line);
++}
+diff --git a/lib/ruby_stacktrace.c b/lib/ruby_stacktrace.c
+new file mode 100644
+index 0000000..4adde08
+--- /dev/null
++++ b/lib/ruby_stacktrace.c
+@@ -0,0 +1,387 @@
++/*
++    ruby_stacktrace.c
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "location.h"
++#include "utils.h"
++#include "json.h"
++#include "sha1.h"
++#include "report_type.h"
++#include "strbuf.h"
++#include "generic_stacktrace.h"
++#include "generic_thread.h"
++#include "internal_utils.h"
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <inttypes.h>
++
++/* Method tables */
++
++static void
++ruby_append_bthash_text(struct sr_ruby_stacktrace *stacktrace, enum sr_bthash_flags flags,
++                          struct sr_strbuf *strbuf);
++
++DEFINE_FRAMES_FUNC(ruby_frames, struct sr_ruby_stacktrace)
++DEFINE_SET_FRAMES_FUNC(ruby_set_frames, struct sr_ruby_stacktrace)
++DEFINE_PARSE_WRAPPER_FUNC(ruby_parse, SR_REPORT_RUBY)
++
++struct thread_methods ruby_thread_methods =
++{
++    .frames = (frames_fn_t) ruby_frames,
++    .set_frames = (set_frames_fn_t) ruby_set_frames,
++    .cmp = (thread_cmp_fn_t) NULL,
++    .frame_count = (frame_count_fn_t) thread_frame_count,
++    .next = (next_thread_fn_t) thread_no_next_thread,
++    .set_next = (set_next_thread_fn_t) NULL,
++    .thread_append_bthash_text =
++        (thread_append_bthash_text_fn_t) thread_no_bthash_text,
++    .thread_free = (thread_free_fn_t) sr_ruby_stacktrace_free,
++    .remove_frame = (remove_frame_fn_t) thread_remove_frame,
++    .remove_frames_above =
++        (remove_frames_above_fn_t) thread_remove_frames_above,
++    .thread_dup = (thread_dup_fn_t) sr_ruby_stacktrace_dup,
++    .normalize = (normalize_fn_t) thread_no_normalization,
++};
++
++struct stacktrace_methods ruby_stacktrace_methods =
++{
++    .parse = (parse_fn_t) ruby_parse,
++    .parse_location = (parse_location_fn_t) sr_ruby_stacktrace_parse,
++    .to_short_text = (to_short_text_fn_t) stacktrace_to_short_text,
++    .to_json = (to_json_fn_t) sr_ruby_stacktrace_to_json,
++    .from_json = (from_json_fn_t) sr_ruby_stacktrace_from_json,
++    .get_reason = (get_reason_fn_t) sr_ruby_stacktrace_get_reason,
++    .find_crash_thread = (find_crash_thread_fn_t) stacktrace_one_thread_only,
++    .threads = (threads_fn_t) stacktrace_one_thread_only,
++    .set_threads = (set_threads_fn_t) NULL,
++    .stacktrace_free = (stacktrace_free_fn_t) sr_ruby_stacktrace_free,
++    .stacktrace_append_bthash_text =
++        (stacktrace_append_bthash_text_fn_t) ruby_append_bthash_text,
++};
++
++/* Public functions */
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_new()
++{
++    struct sr_ruby_stacktrace *stacktrace =
++        sr_malloc(sizeof(struct sr_ruby_stacktrace));
++
++    sr_ruby_stacktrace_init(stacktrace);
++    return stacktrace;
++}
++
++void
++sr_ruby_stacktrace_init(struct sr_ruby_stacktrace *stacktrace)
++{
++    memset(stacktrace, 0, sizeof(struct sr_ruby_stacktrace));
++    stacktrace->type = SR_REPORT_RUBY;
++}
++
++void
++sr_ruby_stacktrace_free(struct sr_ruby_stacktrace *stacktrace)
++{
++    if (!stacktrace)
++        return;
++
++    while (stacktrace->frames)
++    {
++        struct sr_ruby_frame *frame = stacktrace->frames;
++        stacktrace->frames = frame->next;
++        sr_ruby_frame_free(frame);
++    }
++
++    free(stacktrace->exception_name);
++    free(stacktrace);
++}
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_dup(struct sr_ruby_stacktrace *stacktrace)
++{
++    struct sr_ruby_stacktrace *result = sr_ruby_stacktrace_new();
++    memcpy(result, stacktrace, sizeof(struct sr_ruby_stacktrace));
++
++    if (result->exception_name)
++        result->exception_name = sr_strdup(result->exception_name);
++
++    if (result->frames)
++        result->frames = sr_ruby_frame_dup(result->frames, true);
++
++    return result;
++}
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_parse(const char **input,
++                         struct sr_location *location)
++{
++    const char *local_input = *input;
++    struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_new();
++    char *message_and_class = NULL;
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++     */
++    stacktrace->frames = sr_ruby_frame_parse(&local_input, location);
++    if (!stacktrace->frames)
++    {
++        location->message = sr_asprintf("Topmost stacktrace frame not found: %s",
++                            location->message ? location->message : "(unknown reason)");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                              ^^
++     */
++    if (!sr_skip_string(&local_input, ": "))
++    {
++        location->message = sr_strdup("Unable to find the colon after first function name.");
++        goto fail;
++    }
++    location->column += strlen(": ");
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++     */
++    if (!sr_parse_char_cspan(&local_input, "\t", &message_and_class))
++    {
++        location->message = sr_strdup("Unable to find the exception type and message.");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                                    ^^
++     */
++    size_t l = strlen(message_and_class);
++    location->column += l;
++
++    char *p = message_and_class + l - 1;
++    if (p < message_and_class || *p != '\n')
++    {
++        location->column--;
++        location->message = sr_strdup("Unable to find the new line character after "
++                                      "the end of exception class");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                                   ^
++     */
++    p--;
++    if (p < message_and_class || *p != ')')
++    {
++        location->column -= 2;
++        location->message = sr_strdup("Unable to find the ')' character identifying "
++                                      "the end of exception class");
++        goto fail;
++    }
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                   ^^^^^^^^^^^^^^^^
++     */
++    *p = '\0';
++    do {
++        p--;
++    } while (p >= message_and_class && *p != '(');
++    p++;
++
++    if (strlen(p) <= 0)
++    {
++        location->message = sr_strdup("Unable to find the '(' character identifying "
++                                      "the beginning of the exception class");
++        goto fail;
++    }
++    stacktrace->exception_name = sr_strdup(p);
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                                  ^
++     */
++    p--;
++    if (p < message_and_class || *p != '(')
++    {
++        location->message = sr_strdup("Unable to find the '(' character identifying "
++                                      "the beginning of the exception class");
++        goto fail;
++    }
++
++    /* Throw away the message, it may contain sensitive data. */
++    free(message_and_class);
++    message_and_class = p = NULL;
++
++    /* /some/thing.rb:13:in `method': exception message (Exception::Class)\n\tfrom ...
++     *                                   we're here now, increment the line ^^
++     */
++    location->column = 0;
++    location->line++;
++
++    struct sr_ruby_frame *last_frame = stacktrace->frames;
++    while (*local_input)
++    {
++        /* The exception message can continue on the lines after the topmost frame
++         * - skip those.
++         */
++        int skipped = sr_skip_string(&local_input, "\tfrom ");
++        if (!skipped)
++        {
++            location->message = sr_strdup("Frame header not found.");
++            goto fail;
++        }
++        location->column += skipped;
++
++        last_frame->next = sr_ruby_frame_parse(&local_input, location);
++        if (!last_frame->next)
++        {
++            /* location->message is already set */
++            goto fail;
++        }
++
++        /* Eat newline (except at the end of file). */
++        if (!sr_skip_char(&local_input, '\n') && *local_input != '\0')
++        {
++            location->message = sr_strdup("Expected newline after stacktrace frame.");
++            goto fail;
++        }
++        location->column = 0;
++        location->line++;
++        last_frame = last_frame->next;
++    }
++
++    *input = local_input;
++    return stacktrace;
++
++fail:
++    sr_ruby_stacktrace_free(stacktrace);
++    free(message_and_class);
++    return NULL;
++}
++
++char *
++sr_ruby_stacktrace_to_json(struct sr_ruby_stacktrace *stacktrace)
++{
++    struct sr_strbuf *strbuf = sr_strbuf_new();
++
++    /* Exception class name. */
++    if (stacktrace->exception_name)
++    {
++        sr_strbuf_append_str(strbuf, ",   \"exception_name\": ");
++        sr_json_append_escaped(strbuf, stacktrace->exception_name);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
++    /* Frames. */
++    if (stacktrace->frames)
++    {
++        struct sr_ruby_frame *frame = stacktrace->frames;
++        sr_strbuf_append_str(strbuf, ",   \"stacktrace\":\n");
++        while (frame)
++        {
++            if (frame == stacktrace->frames)
++                sr_strbuf_append_str(strbuf, "      [ ");
++            else
++                sr_strbuf_append_str(strbuf, "      , ");
++
++            char *frame_json = sr_ruby_frame_to_json(frame);
++            char *indented_frame_json = sr_indent_except_first_line(frame_json, 8);
++            sr_strbuf_append_str(strbuf, indented_frame_json);
++            free(indented_frame_json);
++            free(frame_json);
++            frame = frame->next;
++            if (frame)
++                sr_strbuf_append_str(strbuf, "\n");
++        }
++
++        sr_strbuf_append_str(strbuf, " ]\n");
++    }
++
++    if (strbuf->len > 0)
++        strbuf->buf[0] = '{';
++    else
++        sr_strbuf_append_char(strbuf, '{');
++
++    sr_strbuf_append_char(strbuf, '}');
++    return sr_strbuf_free_nobuf(strbuf);
++}
++
++struct sr_ruby_stacktrace *
++sr_ruby_stacktrace_from_json(struct sr_json_value *root, char **error_message)
++{
++    if (!JSON_CHECK_TYPE(root, SR_JSON_OBJECT, "stacktrace"))
++        return NULL;
++
++    struct sr_ruby_stacktrace *result = sr_ruby_stacktrace_new();
++
++    /* Exception name. */
++    if (!JSON_READ_STRING(root, "exception_name", &result->exception_name))
++        goto fail;
++
++    /* Frames. */
++    struct sr_json_value *stacktrace = json_element(root, "stacktrace");
++    if (stacktrace)
++    {
++        if (!JSON_CHECK_TYPE(stacktrace, SR_JSON_ARRAY, "stacktrace"))
++            goto fail;
++
++        struct sr_json_value *frame_json;
++        FOR_JSON_ARRAY(stacktrace, frame_json)
++        {
++            struct sr_ruby_frame *frame = sr_ruby_frame_from_json(frame_json,
++                error_message);
++
++            if (!frame)
++                goto fail;
++
++            result->frames = sr_ruby_frame_append(result->frames, frame);
++        }
++    }
++
++    return result;
++
++fail:
++    sr_ruby_stacktrace_free(result);
++    return NULL;
++}
++
++char *
++sr_ruby_stacktrace_get_reason(struct sr_ruby_stacktrace *stacktrace)
++{
++    char *exc = "Unknown error";
++    char *file = "<unknown>";
++    uint32_t line = 0;
++
++    struct sr_ruby_frame *frame = stacktrace->frames;
++    if (frame)
++    {
++        file = frame->file_name;
++        line = frame->file_line;
++    }
++
++    if (stacktrace->exception_name)
++        exc = stacktrace->exception_name;
++
++    return sr_asprintf("%s in %s:%"PRIu32, exc, file, line);
++}
++
++static void
++ruby_append_bthash_text(struct sr_ruby_stacktrace *stacktrace, enum sr_bthash_flags flags,
++                        struct sr_strbuf *strbuf)
++{
++    sr_strbuf_append_strf(strbuf, "Exception: %s\n", OR_UNKNOWN(stacktrace->exception_name));
++    sr_strbuf_append_char(strbuf, '\n');
++}
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index d69f493..0eb33a5 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -1,5 +1,6 @@
+ EXTRA_DIST = gdb_stacktraces \
+              java_stacktraces \
++             ruby_stacktraces \
+              json_files \
+              kerneloopses \
+              programs \
+@@ -50,6 +51,8 @@ TESTSUITE_AT =		\
+   core_frame.at         \
+   core_thread.at        \
+   core_stacktrace.at    \
++  ruby_frame.at		\
++  ruby_stacktrace.at	\
+   normalize.at  	\
+   metrics.at 		\
+   cluster.at  		\
+diff --git a/tests/ruby_frame.at b/tests/ruby_frame.at
+new file mode 100644
+index 0000000..1b1b9d0
+--- /dev/null
++++ b/tests/ruby_frame.at
+@@ -0,0 +1,285 @@
++# Checking the satyr. -*- Autotest -*-
++
++AT_BANNER([Ruby frames])
++
++AT_TESTFUN([sr_ruby_frame_parse],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++void
++check(char *input, char *file, unsigned line,
++      char *function, bool special, unsigned block_level, unsigned rescue_level)
++{
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_frame *frame;
++  frame = sr_ruby_frame_parse(&input, &location);
++
++  assert(frame);
++  assert(frame->file_name && 0 == strcmp(frame->file_name, file));
++  assert(frame->file_line == line);
++  assert(frame->function_name && 0 == strcmp(frame->function_name, function));
++  assert(!!frame->special_function == !!special);
++  assert(frame->block_level == block_level);
++  assert(frame->rescue_level == rescue_level);
++  assert(*input == '\0');
++
++  sr_ruby_frame_free(frame);
++}
++
++int
++main(void)
++{
++  check("/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'",
++        "/usr/share/ruby/vendor_ruby/will_crash.rb", 13, "func", false, 2, 1);
++
++  check("/home/u/work/will:crash/will_crash.rb:30:in `block in <class:WillClass>'",
++        "/home/u/work/will:crash/will_crash.rb", 30, "class:WillClass", true, 1, 0);
++
++  check("./will_ruby_raise:8:in `<main>'",
++        "./will_ruby_raise", 8, "main", true, 0, 0);
++
++  /* try failing */
++  struct sr_location location;
++  sr_location_init(&location);
++  char *input = "i;dikasdfxc";
++
++  struct sr_ruby_frame *frame  = sr_ruby_frame_parse(&input, &location);
++  assert(!frame);
++  assert(location.message);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_cmp],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
++  assert(frame1 != frame2);
++
++  frame2->file_line = 9000;
++  assert(0 != sr_ruby_frame_cmp(frame1, frame2));
++
++  sr_ruby_frame_free(frame1);
++  sr_ruby_frame_free(frame2);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_dup],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_dup(frame1, false);
++
++  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
++  assert(frame1 != frame2);
++  assert(frame1->function_name != frame2->function_name);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_append],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_new();
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_new();
++
++  assert(frame1->next == frame2->next);
++  assert(frame1->next == NULL);
++
++  sr_ruby_frame_append(frame1, frame2);
++  assert(frame1->next == frame2);
++
++  sr_ruby_frame_free(frame1);
++  sr_ruby_frame_free(frame2);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_to_json],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  char *expected = "{   \"file_name\": \"/usr/share/ruby/vendor_ruby/will_crash.rb\"\n"
++                   ",   \"file_line\": 13\n"
++                   ",   \"function_name\": \"func\"\n"
++                   ",   \"block_level\": 2\n"
++                   ",   \"rescue_level\": 1\n"
++                   "}";
++
++  char *json = sr_ruby_frame_to_json(frame1);
++  assert(0 == strcmp(json, expected));
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_from_json],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "json.h"
++#include "location.h"
++#include <assert.h>
++
++void
++check(char *input)
++{
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  char *json = sr_ruby_frame_to_json(frame1);
++  char *error = NULL;
++
++  struct sr_json_value *root = sr_json_parse(json, &error);
++  assert(root);
++  struct sr_ruby_frame *frame2 = sr_ruby_frame_from_json(root, &error);
++  assert(frame2);
++  assert(0 == sr_ruby_frame_cmp(frame1, frame2));
++
++  return 0;
++}
++
++int
++main(void)
++{
++  check("/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'");
++  check("/usr/share/ruby/vendor:ruby/will_crash.rb:13:in `block (22 levels) in <func>'");
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_append_to_str],
++[[
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include "strbuf.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  struct sr_strbuf *strbuf = sr_strbuf_new();
++  sr_ruby_frame_append_to_str(frame1, strbuf);
++  char *result = sr_strbuf_free_nobuf(strbuf);
++
++  assert(0 == strcmp(result, "rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13"));
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_frame_generic_functions],
++[[
++#include "ruby/frame.h"
++#include "frame.h"
++#include "utils.h"
++#include "location.h"
++#include "strbuf.h"
++#include <assert.h>
++
++int
++main(void)
++{
++  struct sr_location location;
++  char *line = "/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func'";
++  char *input;
++
++  sr_location_init(&location);
++  input = line;
++  struct sr_ruby_frame *frame1 = sr_ruby_frame_parse(&input, &location);
++  assert(frame1);
++
++  assert(sr_frame_next(frame1) == NULL);
++
++  struct sr_strbuf *strbuf = sr_strbuf_new();
++  sr_frame_append_to_str((struct sr_frame*)frame1, strbuf);
++  char *result = sr_strbuf_free_nobuf(strbuf);
++
++  assert(0 == strcmp(result, "rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13"));
++
++  sr_frame_free((struct sr_frame*)frame1);
++  return 0;
++}
++]])
+diff --git a/tests/ruby_stacktrace.at b/tests/ruby_stacktrace.at
+new file mode 100644
+index 0000000..5a2d901
+--- /dev/null
++++ b/tests/ruby_stacktrace.at
+@@ -0,0 +1,316 @@
++# Checking the satyr. -*- Autotest -*-
++
++AT_BANNER([Ruby stacktrace])
++
++AT_TESTFUN([sr_ruby_stacktrace_parse],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename, char *exc_name, unsigned frame_count, struct sr_ruby_frame *top_frame, struct sr_ruby_frame *second_frame, struct sr_ruby_frame *bottom_frame)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_parse(&input, &location);
++  assert(stacktrace);
++  assert(*input == '\0');
++  assert(0 == strcmp(stacktrace->exception_name, exc_name));
++
++  struct sr_ruby_frame *frame = stacktrace->frames;
++  int i = 0;
++  while (frame)
++  {
++    if (i==0 && top_frame)
++    {
++      assert(sr_ruby_frame_cmp(frame, top_frame) == 0);
++    }
++    else if (i == 1 && second_frame)
++    {
++      assert(sr_ruby_frame_cmp(frame, second_frame) == 0);
++    }
++
++    frame = frame->next;
++    i++;
++  }
++
++  assert(i == frame_count);
++  if (frame && bottom_frame)
++  {
++    assert(sr_ruby_frame_cmp(frame, bottom_frame) == 0);
++  }
++
++  sr_ruby_stacktrace_free(stacktrace);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  struct sr_ruby_frame top = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/ruby/vendor_ruby/will_crash.rb",
++    .file_line = 13,
++    .special_function = false,
++    .function_name = "func",
++    .block_level = 2,
++    .rescue_level = 1
++  };
++  struct sr_ruby_frame second = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/ruby/vendor_ruby/will_crash.rb",
++    .file_line = 10,
++    .special_function = false,
++    .function_name = "func",
++    .block_level = 2,
++    .rescue_level = 0
++  };
++  struct sr_ruby_frame bottom = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/bin/will_ruby_raise",
++    .file_line = 8,
++    .special_function = true,
++    .function_name = "main",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  check("../../ruby_stacktraces/ruby-01", "Wrap::MyException", 21, &top, &second, &bottom);
++
++  struct sr_ruby_frame top2 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb",
++    .file_line = 173,
++    .special_function = false,
++    .function_name = "aref_prefix",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  struct sr_ruby_frame second2 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb",
++    .file_line = 181,
++    .special_function = false,
++    .function_name = "aref",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  check("../../ruby_stacktraces/ruby-02", "NotImplementedError", 37, &top2, &second2, NULL);
++
++  check("../../ruby_stacktraces/ruby-03", "RuntimeError", 4, NULL, NULL, NULL);
++
++  struct sr_ruby_frame top4 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/rubygems/rubygems/basic_specification.rb",
++    .file_line = 50,
++    .special_function = false,
++    .function_name = "contains_requirable_file?",
++    .block_level = 3,
++    .rescue_level = 0
++  };
++  struct sr_ruby_frame second4 = {
++    .type = SR_REPORT_RUBY,
++    .file_name = "/usr/share/rubygems/rubygems/basic_specification.rb",
++    .file_line = 50,
++    .special_function = false,
++    .function_name = "each",
++    .block_level = 0,
++    .rescue_level = 0
++  };
++  check("../../ruby_stacktraces/ruby-04", "SignalException", 39, &top4, &second4, NULL);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_dup],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++  struct sr_ruby_stacktrace *stacktrace2 = sr_ruby_stacktrace_dup(stacktrace1);
++
++  assert(stacktrace1 != stacktrace2);
++  assert(0 == strcmp(stacktrace1->exception_name, stacktrace2->exception_name));
++
++  struct sr_ruby_frame *f1 = stacktrace1->frames;
++  struct sr_ruby_frame *f2 = stacktrace2->frames;
++
++  while (f1 && f2)
++  {
++    assert(0 == sr_ruby_frame_cmp(f1, f2));
++    f1 = f1->next;
++    f2 = f2->next;
++  }
++  assert(f1 == NULL);
++  assert(f2 == NULL);
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  sr_ruby_stacktrace_free(stacktrace2);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  check("../../ruby_stacktraces/ruby-01");
++  check("../../ruby_stacktraces/ruby-02");
++  check("../../ruby_stacktraces/ruby-03");
++  check("../../ruby_stacktraces/ruby-04");
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_get_reason],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++int
++main(void)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string("../../ruby_stacktraces/ruby-03", &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++
++  char *reason = sr_ruby_stacktrace_get_reason(stacktrace1);
++  char *expected = "RuntimeError in /usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:103";
++  assert(0 == strcmp(reason, expected));
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  free(file_contents);
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_to_json],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename, char *json_filename)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++
++  char *expected = sr_file_to_string(json_filename, &error_message);
++  char *json = sr_ruby_stacktrace_to_json(stacktrace1);
++
++  assert(0 == strcmp(json, expected));
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  free(json);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  check("../../ruby_stacktraces/ruby-01", "../../ruby_stacktraces/ruby-01-expected-json");
++  check("../../ruby_stacktraces/ruby-02", "../../ruby_stacktraces/ruby-02-expected-json");
++  check("../../ruby_stacktraces/ruby-03", "../../ruby_stacktraces/ruby-03-expected-json");
++  check("../../ruby_stacktraces/ruby-04", "../../ruby_stacktraces/ruby-04-expected-json");
++
++  return 0;
++}
++]])
++
++AT_TESTFUN([sr_ruby_stacktrace_from_json],
++[[
++#include "ruby/stacktrace.h"
++#include "ruby/frame.h"
++#include "utils.h"
++#include "location.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void
++check(char *filename)
++{
++  char *error_message = NULL;
++  const char *file_contents = sr_file_to_string(filename, &error_message);
++  const char *input = file_contents;
++  struct sr_location location;
++  sr_location_init(&location);
++
++  struct sr_ruby_stacktrace *stacktrace1 = sr_ruby_stacktrace_parse(&input, &location);
++
++  char *json = sr_ruby_stacktrace_to_json(stacktrace1);
++  struct sr_ruby_stacktrace *stacktrace2 = sr_stacktrace_from_json_text(SR_REPORT_RUBY, json, &error_message);
++
++  assert(0 == strcmp(stacktrace1->exception_name, stacktrace2->exception_name));
++
++  struct sr_ruby_frame *f1 = stacktrace1->frames;
++  struct sr_ruby_frame *f2 = stacktrace2->frames;
++
++  while (f1 && f2)
++  {
++    assert(0 == sr_ruby_frame_cmp(f1, f2));
++    f1 = f1->next;
++    f2 = f2->next;
++  }
++  assert(f1 == NULL);
++  assert(f2 == NULL);
++
++  sr_ruby_stacktrace_free(stacktrace1);
++  sr_ruby_stacktrace_free(stacktrace2);
++  free(json);
++  free(file_contents);
++}
++
++int
++main(void)
++{
++  check("../../ruby_stacktraces/ruby-01");
++  check("../../ruby_stacktraces/ruby-02");
++  check("../../ruby_stacktraces/ruby-03");
++  check("../../ruby_stacktraces/ruby-04");
++
++  return 0;
++}
++]])
+diff --git a/tests/ruby_stacktraces/ruby-01 b/tests/ruby_stacktraces/ruby-01
+new file mode 100644
+index 0000000..a635dd9
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-01
+@@ -0,0 +1,23 @@
++/usr/share/ruby/vendor_ruby/will_crash.rb:13:in `rescue in block (2 levels) in func': Exception
++successfully
++raised. (Wrap::MyException)
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:10:in `block (2 levels) in func'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:9:in `times'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:9:in `block in func'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:8:in `times'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:8:in `func'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:30:in `block in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:28:in `times'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:28:in `rescue in rescue in rescue in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:25:in `rescue in rescue in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:22:in `rescue in <class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:19:in `<class:WillClass>'
++	from /usr/share/ruby/vendor_ruby/will_crash.rb:6:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/bin/will_ruby_raise:11:in `block (2 levels) in <module:WillModule>'
++	from /usr/bin/will_ruby_raise:10:in `times'
++	from /usr/bin/will_ruby_raise:10:in `block in <module:WillModule>'
++	from /usr/bin/will_ruby_raise:9:in `times'
++	from /usr/bin/will_ruby_raise:9:in `<module:WillModule>'
++	from /usr/bin/will_ruby_raise:8:in `<main>'
+diff --git a/tests/ruby_stacktraces/ruby-01-expected-json b/tests/ruby_stacktraces/ruby-01-expected-json
+new file mode 100644
+index 0000000..493fb18
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-01-expected-json
+@@ -0,0 +1,97 @@
++{   "exception_name": "Wrap::MyException"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 13
++        ,   "function_name": "func"
++        ,   "block_level": 2
++        ,   "rescue_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 10
++        ,   "function_name": "func"
++        ,   "block_level": 2
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 9
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 9
++        ,   "function_name": "func"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 8
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 8
++        ,   "function_name": "func"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 30
++        ,   "special_function": "class:WillClass"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 28
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 28
++        ,   "special_function": "class:WillClass"
++        ,   "rescue_level": 3
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 25
++        ,   "special_function": "class:WillClass"
++        ,   "rescue_level": 2
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 22
++        ,   "special_function": "class:WillClass"
++        ,   "rescue_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 19
++        ,   "special_function": "class:WillClass"
++        }
++      , {   "file_name": "/usr/share/ruby/vendor_ruby/will_crash.rb"
++        ,   "file_line": 6
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 11
++        ,   "special_function": "module:WillModule"
++        ,   "block_level": 2
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 10
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 10
++        ,   "special_function": "module:WillModule"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 9
++        ,   "function_name": "times"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 9
++        ,   "special_function": "module:WillModule"
++        }
++      , {   "file_name": "/usr/bin/will_ruby_raise"
++        ,   "file_line": 8
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-02 b/tests/ruby_stacktraces/ruby-02
+new file mode 100644
+index 0000000..dd24a02
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-02
+@@ -0,0 +1,37 @@
++/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb:173:in `aref_prefix': missing aref_prefix for RDoc::SingleClass (NotImplementedError)
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb:181:in `aref'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/template/darkfish/class.rhtml:47:in `block in generate_class'
++	from /usr/share/ruby/erb.rb:850:in `eval'
++	from /usr/share/ruby/erb.rb:850:in `result'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:724:in `template_result'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:703:in `block in render_template'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `open'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `open'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:698:in `render_template'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:351:in `generate_class'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:371:in `block in generate_class_files'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:368:in `each'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:368:in `generate_class_files'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb:245:in `generate'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:137:in `block in document'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:134:in `chdir'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:134:in `document'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:202:in `generate'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:56:in `block in generation_hook'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:55:in `each'
++	from /usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb:55:in `generation_hook'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:188:in `call'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:188:in `block in install'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:187:in `each'
++	from /usr/local/share/ruby/site_ruby/rubygems/request_set.rb:187:in `install'
++	from /usr/local/share/ruby/site_ruby/rubygems/dependency_installer.rb:394:in `install'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:211:in `update_gem'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:223:in `block in update_gems'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:222:in `each'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:222:in `update_gems'
++	from /usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb:98:in `execute'
++	from /usr/local/share/ruby/site_ruby/rubygems/command.rb:307:in `invoke_with_build_args'
++	from /usr/local/share/ruby/site_ruby/rubygems/command_manager.rb:168:in `process_args'
++	from /usr/local/share/ruby/site_ruby/rubygems/command_manager.rb:138:in `run'
++	from /usr/local/share/ruby/site_ruby/rubygems/gem_runner.rb:54:in `run'
++	from /bin/gem:21:in `<main>'
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-02-expected-json b/tests/ruby_stacktraces/ruby-02-expected-json
+new file mode 100644
+index 0000000..1bef95c
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-02-expected-json
+@@ -0,0 +1,158 @@
++{   "exception_name": "NotImplementedError"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb"
++        ,   "file_line": 173
++        ,   "function_name": "aref_prefix"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/class_module.rb"
++        ,   "file_line": 181
++        ,   "function_name": "aref"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/template/darkfish/class.rhtml"
++        ,   "file_line": 47
++        ,   "function_name": "generate_class"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/ruby/erb.rb"
++        ,   "file_line": 850
++        ,   "function_name": "eval"
++        }
++      , {   "file_name": "/usr/share/ruby/erb.rb"
++        ,   "file_line": 850
++        ,   "function_name": "result"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 724
++        ,   "function_name": "template_result"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 703
++        ,   "function_name": "render_template"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 698
++        ,   "function_name": "open"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 698
++        ,   "function_name": "open"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 698
++        ,   "function_name": "render_template"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 351
++        ,   "function_name": "generate_class"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 371
++        ,   "function_name": "generate_class_files"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 368
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 368
++        ,   "function_name": "generate_class_files"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/generator/darkfish.rb"
++        ,   "file_line": 245
++        ,   "function_name": "generate"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 137
++        ,   "function_name": "document"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 134
++        ,   "function_name": "chdir"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 134
++        ,   "function_name": "document"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 202
++        ,   "function_name": "generate"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 56
++        ,   "function_name": "generation_hook"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 55
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/gems/gems/rdoc-4.1.1/lib/rdoc/rubygems_hook.rb"
++        ,   "file_line": 55
++        ,   "function_name": "generation_hook"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 188
++        ,   "function_name": "call"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 188
++        ,   "function_name": "install"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 187
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/request_set.rb"
++        ,   "file_line": 187
++        ,   "function_name": "install"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/dependency_installer.rb"
++        ,   "file_line": 394
++        ,   "function_name": "install"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 211
++        ,   "function_name": "update_gem"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 223
++        ,   "function_name": "update_gems"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 222
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 222
++        ,   "function_name": "update_gems"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/commands/update_command.rb"
++        ,   "file_line": 98
++        ,   "function_name": "execute"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command.rb"
++        ,   "file_line": 307
++        ,   "function_name": "invoke_with_build_args"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command_manager.rb"
++        ,   "file_line": 168
++        ,   "function_name": "process_args"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/command_manager.rb"
++        ,   "file_line": 138
++        ,   "function_name": "run"
++        }
++      , {   "file_name": "/usr/local/share/ruby/site_ruby/rubygems/gem_runner.rb"
++        ,   "file_line": 54
++        ,   "function_name": "run"
++        }
++      , {   "file_name": "/bin/gem"
++        ,   "file_line": 21
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-03 b/tests/ruby_stacktraces/ruby-03
+new file mode 100644
+index 0000000..d931254
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-03
+@@ -0,0 +1,4 @@
++/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:103:in `get_interface_mtu': Unable to determine external network interface IP address. (RuntimeError)
++	from /usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb:77:in `initialize'
++	from /sbin/oo-admin-ctl-tc:24:in `new'
++	from /sbin/oo-admin-ctl-tc:24:in `<main>'
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-03-expected-json b/tests/ruby_stacktraces/ruby-03-expected-json
+new file mode 100644
+index 0000000..a1fa1f8
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-03-expected-json
+@@ -0,0 +1,19 @@
++{   "exception_name": "RuntimeError"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb"
++        ,   "file_line": 103
++        ,   "function_name": "get_interface_mtu"
++        }
++      , {   "file_name": "/usr/share/gems/gems/openshift-origin-node-1.18.0.1/lib/openshift-origin-node/utils/tc.rb"
++        ,   "file_line": 77
++        ,   "function_name": "initialize"
++        }
++      , {   "file_name": "/sbin/oo-admin-ctl-tc"
++        ,   "file_line": 24
++        ,   "function_name": "new"
++        }
++      , {   "file_name": "/sbin/oo-admin-ctl-tc"
++        ,   "file_line": 24
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-04 b/tests/ruby_stacktraces/ruby-04
+new file mode 100644
+index 0000000..12388a7
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-04
+@@ -0,0 +1,39 @@
++/usr/share/rubygems/rubygems/basic_specification.rb:50:in `block (3 levels) in contains_requirable_file?': SIGTERM (SignalException)
++	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `each'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `any?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:50:in `block (2 levels) in contains_requirable_file?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `each'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `any?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:49:in `block in contains_requirable_file?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `each'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `any?'
++	from /usr/share/rubygems/rubygems/basic_specification.rb:45:in `contains_requirable_file?'
++	from /usr/share/rubygems/rubygems/specification.rb:898:in `block in find_inactive_by_path'
++	from /usr/share/rubygems/rubygems/specification.rb:897:in `each'
++	from /usr/share/rubygems/rubygems/specification.rb:897:in `find'
++	from /usr/share/rubygems/rubygems/specification.rb:897:in `find_inactive_by_path'
++	from /usr/share/rubygems/rubygems.rb:183:in `try_activate'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:144:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb:2:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines.rb:25:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites.rb:18:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions.rb:10:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:5:in `block in <top (required)>'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:4:in `each'
++	from /usr/share/gems/gems/compass-0.12.2/lib/compass.rb:4:in `<top (required)>'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
++	from /usr/share/gems/gems/compass-0.12.2/bin/compass:20:in `block in <top (required)>'
++	from /usr/share/gems/gems/compass-0.12.2/bin/compass:8:in `fallback_load_path'
++	from /usr/share/gems/gems/compass-0.12.2/bin/compass:19:in `<top (required)>'
++	from /usr/bin/compass:23:in `load'
++	from /usr/bin/compass:23:in `<main>'
+\ No newline at end of file
+diff --git a/tests/ruby_stacktraces/ruby-04-expected-json b/tests/ruby_stacktraces/ruby-04-expected-json
+new file mode 100644
+index 0000000..22bca55
+--- /dev/null
++++ b/tests/ruby_stacktraces/ruby-04-expected-json
+@@ -0,0 +1,166 @@
++{   "exception_name": "SignalException"
++,   "stacktrace":
++      [ {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "contains_requirable_file?"
++        ,   "block_level": 3
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "any?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 50
++        ,   "function_name": "contains_requirable_file?"
++        ,   "block_level": 2
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 49
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 49
++        ,   "function_name": "any?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 49
++        ,   "function_name": "contains_requirable_file?"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 45
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 45
++        ,   "function_name": "any?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/basic_specification.rb"
++        ,   "file_line": 45
++        ,   "function_name": "contains_requirable_file?"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 898
++        ,   "function_name": "find_inactive_by_path"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 897
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 897
++        ,   "function_name": "find"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/specification.rb"
++        ,   "file_line": 897
++        ,   "function_name": "find_inactive_by_path"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems.rb"
++        ,   "file_line": 183
++        ,   "function_name": "try_activate"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 132
++        ,   "function_name": "require"
++        ,   "rescue_level": 1
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 144
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines/chunky_png_engine.rb"
++        ,   "file_line": 2
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites/engines.rb"
++        ,   "file_line": 25
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions/sprites.rb"
++        ,   "file_line": 18
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass/sass_extensions.rb"
++        ,   "file_line": 10
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
++        ,   "file_line": 5
++        ,   "special_function": "top (required)"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
++        ,   "file_line": 4
++        ,   "function_name": "each"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/lib/compass.rb"
++        ,   "file_line": 4
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/rubygems/rubygems/core_ext/kernel_require.rb"
++        ,   "file_line": 55
++        ,   "function_name": "require"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
++        ,   "file_line": 20
++        ,   "special_function": "top (required)"
++        ,   "block_level": 1
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
++        ,   "file_line": 8
++        ,   "function_name": "fallback_load_path"
++        }
++      , {   "file_name": "/usr/share/gems/gems/compass-0.12.2/bin/compass"
++        ,   "file_line": 19
++        ,   "special_function": "top (required)"
++        }
++      , {   "file_name": "/usr/bin/compass"
++        ,   "file_line": 23
++        ,   "function_name": "load"
++        }
++      , {   "file_name": "/usr/bin/compass"
++        ,   "file_line": 23
++        ,   "special_function": "main"
++        } ]
++}
+\ No newline at end of file
+diff --git a/tests/testsuite.at b/tests/testsuite.at
+index 904fe64..f27ed13 100644
+--- a/tests/testsuite.at
++++ b/tests/testsuite.at
+@@ -15,6 +15,8 @@ m4_include([koops_stacktrace.at])
+ m4_include([core_frame.at])
+ m4_include([core_thread.at])
+ m4_include([core_stacktrace.at])
++m4_include([ruby_frame.at])
++m4_include([ruby_stacktrace.at])
+ m4_include([operating_system.at])
+ m4_include([normalize.at])
+ m4_include([metrics.at])
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch b/SOURCES/satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch
new file mode 100644
index 0000000..5fd7e50
--- /dev/null
+++ b/SOURCES/satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch
@@ -0,0 +1,38 @@
+From 309b839354b171cd03955537102ca73bc14b7f58 Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Mon, 16 May 2016 14:19:34 +0200
+Subject: [PATCH] Check the return value of sr_parse_char_cspan
+
+Related to: #1336390
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ lib/python_stacktrace.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/lib/python_stacktrace.c b/lib/python_stacktrace.c
+index 99aa52c..557b728 100644
+--- a/lib/python_stacktrace.c
++++ b/lib/python_stacktrace.c
+@@ -231,9 +231,15 @@ sr_python_stacktrace_parse(const char **input,
+     }
+ 
+     /* Parse exception name. */
+-    sr_parse_char_cspan(&local_input,
+-                        ":\n",
+-                        &stacktrace->exception_name);
++    if (!sr_parse_char_cspan(&local_input, ":\n", &stacktrace->exception_name))
++    {
++
++        location->message = "Unable to find the ':\\n' characters "
++                            "identifying the end of exception name.";
++        sr_python_stacktrace_free(stacktrace);
++        return NULL;
++
++    }
+ 
+     *input = local_input;
+     return stacktrace;
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-Fix-defects-found-by-coverity.patch b/SOURCES/satyr-0.13-Fix-defects-found-by-coverity.patch
new file mode 100644
index 0000000..4fa343c
--- /dev/null
+++ b/SOURCES/satyr-0.13-Fix-defects-found-by-coverity.patch
@@ -0,0 +1,282 @@
+From 481ac50e3e9ad53e4f07dce04facfaef5a2cdcfd Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Thu, 5 Mar 2015 14:08:15 +0100
+Subject: [PATCH] Fix defects found by coverity
+
+Resolves: #1336390
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+
+Conflicts:
+	lib/abrt.c
+---
+ lib/core_stacktrace.c      |  8 ++++++++
+ lib/core_unwind.c          |  6 ++++--
+ lib/java_frame.c           |  3 +++
+ lib/koops_stacktrace.c     |  3 +++
+ lib/python_frame.c         | 26 ++++++++++++++------------
+ lib/rpm.c                  |  4 +++-
+ lib/utils.c                |  1 +
+ python/py_gdb_stacktrace.c |  8 ++++++++
+ satyr.c                    |  3 +++
+ 9 files changed, 47 insertions(+), 15 deletions(-)
+
+diff --git a/lib/core_stacktrace.c b/lib/core_stacktrace.c
+index f7fd369..aeb97e4 100644
+--- a/lib/core_stacktrace.c
++++ b/lib/core_stacktrace.c
+@@ -192,7 +192,10 @@ sr_core_stacktrace_from_json(struct sr_json_value *root,
+             if (crash_thread)
+             {
+                 if (!JSON_CHECK_TYPE(crash_thread, SR_JSON_BOOLEAN, "crash_thread"))
++                {
++                    sr_core_thread_free(thread);
+                     goto fail;
++                }
+ 
+                 if (crash_thread->u.boolean)
+                     result->crash_thread = thread;
+@@ -296,6 +299,7 @@ sr_core_stacktrace_create(const char *gdb_stacktrace_text,
+     {
+         warn("Unable to parse unstrip output.");
+ 
++        sr_gdb_stacktrace_free(gdb_stacktrace);
+         return NULL;
+     }
+ 
+@@ -332,6 +336,8 @@ sr_core_stacktrace_create(const char *gdb_stacktrace_text,
+                 core_frame->function_name =
+                     sr_strdup(gdb_frame->function_name);
+             }
++
++            core_thread->frames = sr_core_frame_append(core_thread->frames, core_frame);
+         }
+ 
+         core_stacktrace->threads =
+@@ -341,6 +347,8 @@ sr_core_stacktrace_create(const char *gdb_stacktrace_text,
+         gdb_thread = gdb_thread->next;
+     }
+ 
++    sr_unstrip_free(unstrip);
++    sr_gdb_stacktrace_free(gdb_stacktrace);
+     return core_stacktrace;
+ }
+ 
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index c30f9db..b8c0235 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -127,6 +127,7 @@ find_elf_core (Dwfl_Module *mod, void **userdata, const char *modname,
+         {
+             warn("Unable to open executable '%s': %s", executable_file,
+                  elf_errmsg(-1));
++            close(fd);
+             return -1;
+         }
+ 
+@@ -399,9 +400,10 @@ struct sr_core_stacktrace *
+ sr_core_stacktrace_from_gdb(const char *gdb_output, const char *core_file,
+                             const char *exe_file, char **error_msg)
+ {
++    /* I'm not going to rewrite it now since the function is not being used. */
++    assert(error_msg);
+     /* Initialize error_msg to 'no error'. */
+-    if (error_msg)
+-        *error_msg = NULL;
++    *error_msg = NULL;
+ 
+     struct core_handle *ch = open_coredump(core_file, exe_file, error_msg);
+     if (*error_msg)
+diff --git a/lib/java_frame.c b/lib/java_frame.c
+index ee97572..d03f86a 100644
+--- a/lib/java_frame.c
++++ b/lib/java_frame.c
+@@ -359,7 +359,10 @@ sr_java_frame_parse_exception(const char **input,
+         if (exception->next == NULL)
+             exception->next = parsed;
+         else
++        {
++            assert(frame);
+             frame->next = parsed;
++        }
+ 
+         frame = parsed;
+     }
+diff --git a/lib/koops_stacktrace.c b/lib/koops_stacktrace.c
+index 611c7d1..01baa79 100644
+--- a/lib/koops_stacktrace.c
++++ b/lib/koops_stacktrace.c
+@@ -506,7 +506,10 @@ taint_flags_to_json(struct sr_koops_stacktrace *stacktrace)
+     }
+ 
+     if (strbuf->len == 0)
++    {
++        sr_strbuf_free(strbuf);
+         return sr_strdup("[]");
++    }
+ 
+     sr_strbuf_append_char(strbuf, ']');
+     char *result = sr_strbuf_free_nobuf(strbuf);
+diff --git a/lib/python_frame.c b/lib/python_frame.c
+index b0540fe..4b4be8a 100644
+--- a/lib/python_frame.c
++++ b/lib/python_frame.c
+@@ -220,11 +220,10 @@ sr_python_frame_parse(const char **input,
+     /* Parse file name */
+     if (!sr_parse_char_cspan(&local_input, "\"", &frame->file_name))
+     {
+-        sr_python_frame_free(frame);
+         location->message = sr_asprintf("Unable to find the '\"' character "
+                 "identifying the beginning of file name.");
+ 
+-        return NULL;
++        goto fail;
+     }
+ 
+     if (strlen(frame->file_name) > 0 &&
+@@ -243,7 +242,7 @@ sr_python_frame_parse(const char **input,
+     if (0 == sr_skip_string(&local_input, "\", line "))
+     {
+         location->message = sr_asprintf("Line separator not found.");
+-        return NULL;
++        goto fail;
+     }
+ 
+     location->column += strlen("\", line ");
+@@ -253,7 +252,7 @@ sr_python_frame_parse(const char **input,
+     if (0 == length)
+     {
+         location->message = sr_asprintf("Line number not found.");
+-        return NULL;
++        goto fail;
+     }
+ 
+     location->column += length;
+@@ -263,7 +262,7 @@ sr_python_frame_parse(const char **input,
+         if (local_input[0] != '\n')
+         {
+             location->message = sr_asprintf("Function name separator not found.");
+-            return NULL;
++            goto fail;
+         }
+ 
+         /* The last frame of SyntaxError stack trace does not have
+@@ -280,11 +279,10 @@ sr_python_frame_parse(const char **input,
+         /* Parse function name */
+         if (!sr_parse_char_cspan(&local_input, "\n", &frame->function_name))
+         {
+-            sr_python_frame_free(frame);
+             location->message = sr_asprintf("Unable to find the newline character "
+                     "identifying the end of function name.");
+ 
+-            return NULL;
++            goto fail;
+         }
+ 
+         location->column += strlen(frame->function_name);
+@@ -301,19 +299,23 @@ sr_python_frame_parse(const char **input,
+         }
+     }
+ 
+-    sr_skip_char(&local_input, '\n');
+-    sr_location_add(location, 1, 0);
++    if (sr_skip_char(&local_input, '\n'))
++        sr_location_add(location, 1, 0);
+ 
+     /* Parse source code line (optional). */
+     if (4 == sr_skip_string(&local_input, "    "))
+     {
+-        sr_parse_char_cspan(&local_input, "\n", &frame->line_contents);
+-        sr_skip_char(&local_input, '\n');
+-        sr_location_add(location, 1, 0);
++        if (sr_parse_char_cspan(&local_input, "\n", &frame->line_contents)
++                && sr_skip_char(&local_input, '\n'))
++            sr_location_add(location, 1, 0);
+     }
+ 
+     *input = local_input;
+     return frame;
++
++fail:
++    sr_python_frame_free(frame);
++    return NULL;
+ }
+ 
+ char *
+diff --git a/lib/rpm.c b/lib/rpm.c
+index f8f1fa6..920e145 100644
+--- a/lib/rpm.c
++++ b/lib/rpm.c
+@@ -199,7 +199,9 @@ sr_rpm_package_sort(struct sr_rpm_package *packages)
+             array[loop]->next = NULL;
+     }
+ 
+-    return array[0];
++    struct sr_rpm_package *result = array[0];
++    free(array);
++    return result;
+ }
+ 
+ static struct sr_rpm_package *
+diff --git a/lib/utils.c b/lib/utils.c
+index fa3c0a0..bdafaa7 100644
+--- a/lib/utils.c
++++ b/lib/utils.c
+@@ -354,6 +354,7 @@ sr_string_to_file(const char *filename,
+                                      filename,
+                                      error);
+ 
++        close(fd);
+         return false;
+     }
+ 
+diff --git a/python/py_gdb_stacktrace.c b/python/py_gdb_stacktrace.c
+index c799e06..5ed3533 100644
+--- a/python/py_gdb_stacktrace.c
++++ b/python/py_gdb_stacktrace.c
+@@ -7,6 +7,7 @@
+ #include "strbuf.h"
+ #include "gdb/stacktrace.h"
+ #include "gdb/thread.h"
++#include "gdb/frame.h"
+ #include "gdb/sharedlib.h"
+ #include "location.h"
+ #include "normalize.h"
+@@ -408,13 +409,20 @@ sr_py_gdb_stacktrace_find_crash_frame(PyObject *self, PyObject *args)
+         PyObject_New(struct sr_py_gdb_frame, &sr_py_gdb_frame_type);
+ 
+     if (!result)
++    {
++        sr_gdb_frame_free(frame);
+         return PyErr_NoMemory();
++    }
+ 
+     result->frame = frame;
+     this->crashframe = result;
+ 
+     if (stacktrace_rebuild_thread_python_list(this) < 0)
++    {
++        sr_gdb_frame_free(frame);
++        Py_DECREF(result);
+         return NULL;
++    }
+ 
+     return (PyObject *)result;
+ }
+diff --git a/satyr.c b/satyr.c
+index 6e1ed28..44bf869 100644
+--- a/satyr.c
++++ b/satyr.c
+@@ -210,6 +210,9 @@ debug_normalize(int argc, char **argv)
+     struct sr_strbuf *strbuf = sr_strbuf_new();
+     sr_gdb_thread_append_to_str(thread, strbuf, false);
+     puts(strbuf->buf);
++
++    free(text);
++    sr_strbuf_free(strbuf);
+ }
+ 
+ static void
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-Honor-frame-number-limit-in-GDB-core-unwinder.patch b/SOURCES/satyr-0.13-Honor-frame-number-limit-in-GDB-core-unwinder.patch
new file mode 100644
index 0000000..e0aeed1
--- /dev/null
+++ b/SOURCES/satyr-0.13-Honor-frame-number-limit-in-GDB-core-unwinder.patch
@@ -0,0 +1,90 @@
+From d94a7b5c516c0446d1c49e8446707a64b4b45148 Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Tue, 2 Aug 2016 08:50:10 +0200
+Subject: [PATCH] Honor frame number limit in GDB core unwinder
+
+Related to #1260074
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ lib/core_unwind.c          | 15 +++++++++++++++
+ lib/core_unwind_elfutils.c |  6 ++----
+ lib/internal_unwind.h      |  5 +++++
+ 3 files changed, 22 insertions(+), 4 deletions(-)
+
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index b8c0235..05b94a5 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -429,6 +429,8 @@ sr_core_stacktrace_from_gdb(const char *gdb_output, const char *core_file,
+     {
+         struct sr_core_thread *core_thread = sr_core_thread_new();
+ 
++        unsigned long nframes = CORE_STACKTRACE_FRAME_LIMIT;
++        struct sr_gdb_frame *top_frame = gdb_thread->frames;
+         for (struct sr_gdb_frame *gdb_frame = gdb_thread->frames;
+              gdb_frame;
+              gdb_frame = gdb_frame->next)
+@@ -436,6 +438,19 @@ sr_core_stacktrace_from_gdb(const char *gdb_output, const char *core_file,
+             if (gdb_frame->signal_handler_called)
+                 continue;
+ 
++            if (nframes)
++                --nframes;
++            else
++                top_frame = top_frame->next;
++        }
++
++        for (struct sr_gdb_frame *gdb_frame = top_frame;
++             gdb_frame;
++             gdb_frame = gdb_frame->next)
++        {
++            if (gdb_frame->signal_handler_called)
++                continue;
++
+             struct sr_core_frame *core_frame = resolve_frame(ch->dwfl,
+                     gdb_frame->address, false);
+ 
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index 1482dba..d273a03 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -33,8 +33,6 @@
+ #include <sys/ptrace.h>
+ #include <sys/wait.h>
+ 
+-#define FRAME_LIMIT 256
+-
+ struct frame_callback_arg
+ {
+     struct sr_core_frame **frames_tail;
+@@ -84,8 +82,8 @@ frame_callback(Dwfl_Frame *frame, void *data)
+ static void
+ truncate_long_thread(struct sr_core_thread *thread, struct frame_callback_arg *frame_arg)
+ {
+-    /* Truncate the stacktrace to FRAME_LIMIT least recent frames. */
+-    while (thread->frames && frame_arg->nframes > FRAME_LIMIT)
++    /* Truncate the stacktrace to CORE_STACKTRACE_FRAME_LIMIT least recent frames. */
++    while (thread->frames && frame_arg->nframes > CORE_STACKTRACE_FRAME_LIMIT)
+     {
+         struct sr_core_frame *old_frame = thread->frames;
+         thread->frames = old_frame->next;
+diff --git a/lib/internal_unwind.h b/lib/internal_unwind.h
+index 84261c6..b9e162a 100644
+--- a/lib/internal_unwind.h
++++ b/lib/internal_unwind.h
+@@ -66,6 +66,11 @@ _set_error(char **error_msg, const char *fmt, ...) __sr_printf(2, 3);
+         }                                    \
+     } while(0)
+ 
++/* This macro is used as a limit for the number of function frames included in
++ * every single thread of a core stacktrace generated by satyr.
++ */
++#define CORE_STACKTRACE_FRAME_LIMIT 256
++
+ struct exe_mapping_data
+ {
+     uint64_t start;
+-- 
+2.17.1
+
diff --git a/SOURCES/satyr-0.13-abrt-refactorize-unwinding-from-core-hook.patch b/SOURCES/satyr-0.13-abrt-refactorize-unwinding-from-core-hook.patch
new file mode 100644
index 0000000..c1821fd
--- /dev/null
+++ b/SOURCES/satyr-0.13-abrt-refactorize-unwinding-from-core-hook.patch
@@ -0,0 +1,99 @@
+From 89e0a7d0a195605585b80157ce23be1fb03f04a2 Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Wed, 13 May 2015 10:45:32 +0200
+Subject: [PATCH] abrt: refactorize unwinding from core hook
+
+ABRT needs to save files from its core hook with O_EXCL and with right
+mode and owner and there might be more constraints ABRT needs to
+respect in future.
+
+This patch breaks granularity of the abrt functions to allow ABRT to save the
+core backtrace data in the way it need by providing a function returning
+the data instead of directly saving the data to a file.
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ include/abrt.h |  6 ++++++
+ lib/abrt.c     | 29 ++++++++++++++++++++++-------
+ 2 files changed, 28 insertions(+), 7 deletions(-)
+
+diff --git a/include/abrt.h b/include/abrt.h
+index 5116446..ca0748c 100644
+--- a/include/abrt.h
++++ b/include/abrt.h
+@@ -44,6 +44,12 @@ sr_abrt_create_core_stacktrace_from_gdb(const char *directory,
+                                         bool hash_fingerprints,
+                                         char **error_message);
+ 
++char *
++sr_abrt_get_core_stacktrace_from_core_hook(pid_t thread_id,
++                                           const char *executable,
++                                           int signum,
++                                           char **error_message);
++
+ bool
+ sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
+                                               pid_t thread_id,
+diff --git a/lib/abrt.c b/lib/abrt.c
+index 86e9082..4599e2c 100644
+--- a/lib/abrt.c
++++ b/lib/abrt.c
+@@ -162,12 +162,11 @@ sr_abrt_create_core_stacktrace(const char *directory,
+                                   error_message);
+ }
+ 
+-bool
+-sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
+-                                              pid_t thread_id,
+-                                              const char *executable,
+-                                              int signum,
+-                                              char **error_message)
++char *
++sr_abrt_get_core_stacktrace_from_core_hook(pid_t thread_id,
++                                           const char *executable,
++                                           int signum,
++                                           char **error_message)
+ {
+ 
+     struct sr_core_stacktrace *core_stacktrace;
+@@ -180,12 +179,29 @@ sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
+     fulfill_missing_values(core_stacktrace);
+ 
+     char *json = sr_core_stacktrace_to_json(core_stacktrace);
++    sr_core_stacktrace_free(core_stacktrace);
+ 
+     // Add newline to the end of core stacktrace file to make text
+     // editors happy.
+     json = sr_realloc(json, strlen(json) + 2);
+     strcat(json, "\n");
+ 
++    return json;
++;
++}
++
++bool
++sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
++                                              pid_t thread_id,
++                                              const char *executable,
++                                              int signum,
++                                              char **error_message)
++{
++    char *json = sr_abrt_get_core_stacktrace_from_core_hook(thread_id,
++                                                            executable,
++                                                            signum,
++                                                            error_message);
++
+     char *core_backtrace_filename = sr_build_path(directory, "core_backtrace", NULL);
+     bool success = sr_string_to_file(core_backtrace_filename,
+                                     json,
+@@ -193,7 +209,6 @@ sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
+ 
+     free(core_backtrace_filename);
+     free(json);
+-    sr_core_stacktrace_free(core_stacktrace);
+     return success;
+ }
+ 
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-better-inf-recursion-handling.patch b/SOURCES/satyr-0.13-better-inf-recursion-handling.patch
new file mode 100644
index 0000000..ba945f4
--- /dev/null
+++ b/SOURCES/satyr-0.13-better-inf-recursion-handling.patch
@@ -0,0 +1,109 @@
+From 9639948f3fdda4afcf25e7080aa87d8ad2fbeb34 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 27 Aug 2014 13:55:50 +0200
+Subject: [SATYR PATCH] Better handling of infinite recursion
+
+unwind: Append threads/frames in O(1)
+unwind: throw away the most recent frames
+unwind: lower stacktrace length limit to 256
+
+Fixes #179.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/core_unwind_elfutils.c | 36 ++++++++++++++++++++----------------
+ 1 file changed, 20 insertions(+), 16 deletions(-)
+
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index 43d66be..bbd4813 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -29,18 +29,18 @@
+ #include <stdio.h>
+ #include <string.h>
+ 
+-#define FRAME_LIMIT 1024
++#define FRAME_LIMIT 256
+ 
+ struct frame_callback_arg
+ {
+-    struct sr_core_thread *thread;
++    struct sr_core_frame **frames_tail;
+     char *error_msg;
+     unsigned nframes;
+ };
+ 
+ struct thread_callback_arg
+ {
+-    struct sr_core_stacktrace *stacktrace;
++    struct sr_core_thread **threads_tail;
+     char *error_msg;
+ };
+ 
+@@ -70,15 +70,9 @@ frame_callback(Dwfl_Frame *frame, void *data)
+         return CB_STOP_UNWIND;
+     }
+ 
+-    frame_arg->thread->frames =
+-        sr_core_frame_append(frame_arg->thread->frames, result);
+-
+-    /* Avoid huge stacktraces from programs stuck in infinite recursion. */
++    *frame_arg->frames_tail = result;
++    frame_arg->frames_tail = &result->next;
+     frame_arg->nframes++;
+-    if (frame_arg->nframes >= FRAME_LIMIT)
+-    {
+-        return CB_STOP_UNWIND;
+-    }
+ 
+     return DWARF_CB_OK;
+ }
+@@ -99,7 +93,7 @@ unwind_thread(Dwfl_Thread *thread, void *data)
+ 
+     struct frame_callback_arg frame_arg =
+     {
+-        .thread = result,
++        .frames_tail = &(result->frames),
+         .error_msg = NULL,
+         .nframes = 0
+     };
+@@ -121,14 +115,24 @@ unwind_thread(Dwfl_Thread *thread, void *data)
+         goto abort;
+     }
+ 
+-    if (!error_msg && !frame_arg.thread->frames)
++    if (!error_msg && !result->frames)
+     {
+         set_error("No frames found for thread id %d", (int)result->id);
+         goto abort;
+     }
+ 
+-    thread_arg->stacktrace->threads =
+-        sr_core_thread_append(thread_arg->stacktrace->threads, result);
++    /* Truncate the stacktrace to FRAME_LIMIT least recent frames. */
++    while (result->frames && frame_arg.nframes > FRAME_LIMIT)
++    {
++        struct sr_core_frame *old_frame = result->frames;
++        result->frames = old_frame->next;
++        sr_core_frame_free(old_frame);
++        frame_arg.nframes--;
++    }
++
++    *thread_arg->threads_tail = result;
++    thread_arg->threads_tail = &result->next;
++
+     return DWARF_CB_OK;
+ 
+ abort:
+@@ -166,7 +170,7 @@ sr_parse_coredump(const char *core_file,
+ 
+     struct thread_callback_arg thread_arg =
+     {
+-        .stacktrace = stacktrace,
++        .threads_tail = &(stacktrace->threads),
+         .error_msg = NULL
+     };
+ 
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-core_unwind-fix-the-missing-frame-build_id-and-file.patch b/SOURCES/satyr-0.13-core_unwind-fix-the-missing-frame-build_id-and-file.patch
new file mode 100644
index 0000000..92b4fc5
--- /dev/null
+++ b/SOURCES/satyr-0.13-core_unwind-fix-the-missing-frame-build_id-and-file.patch
@@ -0,0 +1,49 @@
+From d34232858880672a9e6b88ff5ebd966969aabb9f Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Mon, 7 Sep 2015 08:08:16 +0200
+Subject: [PATCH] core_unwind: fix the missing frame build_id and file_name
+
+The documentation of dwfl_module_build_id() says:
+
+  This returns 0 when the module's main ELF file has not yet been loaded
+  and its build ID bits were not reported.  To ensure the ID is always
+  returned when determinable, call dwfl_module_getelf first.
+
+  /usr/include/elfutils/libdwfl.h
+
+ABRT upstream commit 88c6b3683a129c0e369071204b29fbca94772d3b verifies
+this patch.
+
+Discovered by Richard Marko <rmarko@redhat.com>
+Resolved by Mark Wielaard <mjw@redhat.com>
+
+Related: #1210599
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ lib/core_unwind.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index cf9973a..c30f9db 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -278,9 +278,14 @@ resolve_frame(Dwfl *dwfl, Dwarf_Addr ip, bool minus_one)
+         int ret;
+         const unsigned char *build_id_bits;
+         const char *filename, *funcname;
+-        GElf_Addr bid_addr;
++        GElf_Addr bias, bid_addr;
+         Dwarf_Addr start;
+ 
++        /* Initialize the module's main Elf for dwfl_module_build_id and dwfl_module_info */
++        /* No need to deallocate the variable 'bias' and the return value.*/
++        if (NULL == dwfl_module_getelf(mod, &bias))
++            warn("The module's main Elf was not found");
++
+         ret = dwfl_module_build_id(mod, &build_id_bits, &bid_addr);
+         if (ret > 0)
+         {
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-debug-unwinding-from-core-hook-using-satyr-binary.patch b/SOURCES/satyr-0.13-debug-unwinding-from-core-hook-using-satyr-binary.patch
new file mode 100644
index 0000000..21bd205
--- /dev/null
+++ b/SOURCES/satyr-0.13-debug-unwinding-from-core-hook-using-satyr-binary.patch
@@ -0,0 +1,133 @@
+From 455c47f1f18858c439ff0a0b833ce93c9f79045f Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 24 Nov 2014 14:38:06 +0100
+Subject: [PATCH 4/5] debug unwinding from core hook using satyr binary
+
+Write "|/usr/bin/satyr debug unwind-from-hook %e %i %s" to
+/proc/sys/kernel/core_pattern in order to use this. Before kernel 3.18
+the %i flag is not available - you can use %p for single-thread
+processes.
+
+Related to abrt/abrt#829.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ satyr.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 81 insertions(+)
+
+diff --git a/satyr.c b/satyr.c
+index 7bdaa45..6e1ed28 100644
+--- a/satyr.c
++++ b/satyr.c
+@@ -20,6 +20,8 @@
+ #include "gdb/stacktrace.h"
+ #include "gdb/thread.h"
+ #include "gdb/frame.h"
++#include "core/unwind.h"
++#include "core/stacktrace.h"
+ #include "utils.h"
+ #include "location.h"
+ #include "strbuf.h"
+@@ -37,6 +39,7 @@
+ #include <sysexits.h>
+ #include <assert.h>
+ #include <libgen.h>
++#include <time.h>
+ 
+ static char *g_program_name;
+ 
+@@ -275,6 +278,82 @@ debug_duphash(int argc, char **argv)
+ }
+ 
+ static void
++debug_unwind_from_hook(int argc, char **argv)
++{
++    if (argc != 3)
++    {
++        fprintf(stderr, "Wrong number of arguments.\n");
++        fprintf(stderr, "Usage: satyr debug unwind-from-hook "
++                        "<executable> <tid> <signal>\n");
++        exit(1);
++    }
++
++    char *executable = argv[0];
++    char *end;
++    unsigned long tid = strtoul(argv[1], &end, 10);
++    if (*end != '\0')
++    {
++        fprintf(stderr, "Wrong tid\n");
++        exit(1);
++    }
++
++    unsigned long signum = strtoul(argv[2], &end, 10);
++    if (*end != '\0')
++    {
++        fprintf(stderr, "Wrong signal number\n");
++        exit(1);
++    }
++
++    char *error_message = NULL;
++    struct sr_core_stacktrace *core_stacktrace;
++
++    core_stacktrace = sr_core_stacktrace_from_core_hook(tid, executable, signum,
++                                                        &error_message);
++    if (!core_stacktrace)
++    {
++        fprintf(stderr, "Unwind failed: %s\n", error_message);
++        exit(1);
++    }
++
++    char *json = sr_core_stacktrace_to_json(core_stacktrace);
++    // Add newline to the end of core stacktrace file to make text
++    // editors happy.
++    json = sr_realloc(json, strlen(json) + 2);
++    strcat(json, "\n");
++
++    time_t t = time(NULL);
++    struct tm *tm = localtime(&t);
++    char core_backtrace_filename[256];
++
++    if (tm == NULL)
++    {
++        perror("localtime");
++        exit(1);
++    }
++
++    if (strftime(core_backtrace_filename, sizeof(core_backtrace_filename),
++                 "/tmp/satyr-core-stacktrace-%F-%T.json", tm) == 0)
++    {
++        fprintf(stderr, "stftime failed\n");
++        exit(1);
++    }
++
++
++    bool success = sr_string_to_file(core_backtrace_filename,
++                                    json,
++                                    &error_message);
++
++    if (!success)
++    {
++        fprintf(stderr, "Failed to write stacktrace: %s\n", error_message);
++        exit(1);
++    }
++
++    free(json);
++    sr_core_stacktrace_free(core_stacktrace);
++}
++
++static void
+ debug(int argc, char **argv)
+ {
+     /* Debug command requires a subcommand. */
+@@ -288,6 +367,8 @@ debug(int argc, char **argv)
+         debug_normalize(argc - 1, argv + 1);
+     else if (0 == strcmp(argv[0], "duphash"))
+         debug_duphash(argc - 1, argv + 1);
++    else if (0 == strcmp(argv[0], "unwind-from-hook"))
++        debug_unwind_from_hook(argc - 1, argv + 1);
+     else
+     {
+         fprintf(stderr, "Unknown debug subcommand.\n");
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-disable-fingerprints.patch b/SOURCES/satyr-0.13-disable-fingerprints.patch
new file mode 100644
index 0000000..3f8c6be
--- /dev/null
+++ b/SOURCES/satyr-0.13-disable-fingerprints.patch
@@ -0,0 +1,98 @@
+From 58e874e4a28ce00623db208475b0d9fd22d601ac Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Tue, 14 Jan 2014 13:40:45 +0100
+Subject: [SATYR PATCH 3/3] abrt: disable fingerprinting of core stacktraces
+
+Closes rhbz#1052402.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ include/abrt.h         |  2 ++
+ lib/abrt.c             |  2 ++
+ lib/core_fingerprint.c | 25 +++++++++++++++++++++++++
+ 3 files changed, 29 insertions(+)
+
+diff --git a/include/abrt.h b/include/abrt.h
+index 1ef7876..0a8f5ac 100644
+--- a/include/abrt.h
++++ b/include/abrt.h
+@@ -31,6 +31,8 @@ bool
+ sr_abrt_print_report_from_dir(const char *directory,
+                               char **error_message);
+ 
++/* NOTE: the hash_fingerprints argument has no effect because fingerprint
++ * generation is disabled. */
+ bool
+ sr_abrt_create_core_stacktrace(const char *directory,
+                                bool hash_fingerprints,
+diff --git a/lib/abrt.c b/lib/abrt.c
+index 8c006fa..39bc45d 100644
+--- a/lib/abrt.c
++++ b/lib/abrt.c
+@@ -87,11 +87,13 @@ create_core_stacktrace(const char *directory, const char *gdb_output,
+     if (!core_stacktrace)
+         return false;
+ 
++#if 0
+     sr_core_fingerprint_generate(core_stacktrace,
+                                  error_message);
+ 
+     if (hash_fingerprints)
+         sr_core_fingerprint_hash(core_stacktrace);
++#endif
+ 
+     char *json = sr_core_stacktrace_to_json(core_stacktrace);
+ 
+diff --git a/lib/core_fingerprint.c b/lib/core_fingerprint.c
+index 8c5a228..3c7b5ba 100644
+--- a/lib/core_fingerprint.c
++++ b/lib/core_fingerprint.c
+@@ -17,6 +17,8 @@
+     with this program; if not, write to the Free Software Foundation, Inc.,
+     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
++#include "config.h"
++
+ #include "core/fingerprint.h"
+ #include "core/stacktrace.h"
+ #include "core/frame.h"
+@@ -33,6 +35,8 @@
+ #include <stdlib.h>
+ #include <search.h>
+ 
++#if HAVE_LIBOPCODES
++
+ static void
+ fingerprint_add_bool(struct sr_strbuf *buffer,
+                      const char *name,
+@@ -552,6 +556,27 @@ sr_core_fingerprint_generate_for_binary(struct sr_core_thread *thread,
+     return true;
+ }
+ 
++#else // HAVE_LIBOPCODES
++
++bool
++sr_core_fingerprint_generate(struct sr_core_stacktrace *stacktrace,
++                             char **error_message)
++{
++    *error_message = sr_strdup("satyr compiled without libopcodes");
++    return false;
++}
++
++bool
++sr_core_fingerprint_generate_for_binary(struct sr_core_thread *thread,
++                                        const char *binary_path,
++                                        char **error_message)
++{
++    *error_message = sr_strdup("satyr compiled without libopcodes");
++    return false;
++}
++
++#endif // HAVE_LIBOPCODES
++
+ static void
+ hash_frame (struct sr_core_frame *frame)
+ {
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-disable-hook-unwind-on-kernels-w-o-PTRACE_SEIZE.patch b/SOURCES/satyr-0.13-disable-hook-unwind-on-kernels-w-o-PTRACE_SEIZE.patch
new file mode 100644
index 0000000..c181365
--- /dev/null
+++ b/SOURCES/satyr-0.13-disable-hook-unwind-on-kernels-w-o-PTRACE_SEIZE.patch
@@ -0,0 +1,73 @@
+From c1497bdb2a1deaa467fa75a80b1fb993a7d500f5 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 8 Dec 2014 15:37:17 +0100
+Subject: [PATCH 5/5] Disable hook unwind on kernels w/o PTRACE_SEIZE
+
+Related to abrt/abrt#829.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/core_unwind.c          | 7 ++++---
+ lib/core_unwind_elfutils.c | 6 ++++++
+ 2 files changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index 8599f0f..cf9973a 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -25,6 +25,7 @@
+ #include <string.h>
+ #include <errno.h>
+ #include <sys/procfs.h> /* struct elf_prstatus */
++#include <sys/ptrace.h> /* PTRACE_SEIZE */
+ 
+ #include "utils.h"
+ #include "core/unwind.h"
+@@ -52,19 +53,19 @@ sr_parse_coredump(const char *coredump_filename,
+ 
+ #endif /* !defined WITH_LIBDWFL && !defined WITH_LIBUNWIND */
+ 
+-#if !defined WITH_LIBDWFL
++#if (!defined WITH_LIBDWFL || !defined PTRACE_SEIZE)
+ 
+ struct sr_core_stacktrace *
+ sr_core_stacktrace_from_core_hook(pid_t thread_id,
+                                   const char *executable_filename,
+                                   int signum,
+-                                  char **error_message);
++                                  char **error_message)
+ {
+     *error_message = sr_asprintf("satyr is built without live process unwind support");
+     return NULL;
+ }
+ 
+-#endif /* !defined WITH_LIBDWFL */
++#endif /* !defined WITH_LIBDWFL || !defined PTRACE_SEIZE */
+ 
+ /* FIXME: is there another way to pass the executable name to the find_elf
+  * callback? */
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index eb366cd..1482dba 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -212,6 +212,10 @@ fail:
+     return stacktrace;
+ }
+ 
++/* If PTRACE_SEIZE is not defined (kernel < 3.4), stub function from
++ * core_unwind.c is used. */
++#ifdef PTRACE_SEIZE
++
+ struct sr_core_stacktrace *
+ sr_core_stacktrace_from_core_hook(pid_t tid,
+                                   const char *executable,
+@@ -344,4 +348,6 @@ fail:
+     return stacktrace;
+ }
+ 
++#endif /* PTRACE_SEIZE */
++
+ #endif /* WITH_LIBDWFL */
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-dont-free-gdb-stacktrace.patch b/SOURCES/satyr-0.13-dont-free-gdb-stacktrace.patch
new file mode 100644
index 0000000..163ffb9
--- /dev/null
+++ b/SOURCES/satyr-0.13-dont-free-gdb-stacktrace.patch
@@ -0,0 +1,30 @@
+From 969aedadec7f1f693f53ddaf8dcb1f1d59d5ac72 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Tue, 23 Sep 2014 18:22:27 +0200
+Subject: [SATYR PATCH] Don't free gdb stacktrace if no crash thread found
+
+Possibly related to rhbz#1145697.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/gdb_stacktrace.c | 3 ---
+ 1 file changed, 3 deletions(-)
+
+diff --git a/lib/gdb_stacktrace.c b/lib/gdb_stacktrace.c
+index 8e73e21..3b2465a 100644
+--- a/lib/gdb_stacktrace.c
++++ b/lib/gdb_stacktrace.c
+@@ -558,10 +558,7 @@ sr_gdb_stacktrace_to_short_text(struct sr_gdb_stacktrace *stacktrace,
+     crash_thread = sr_gdb_stacktrace_find_crash_thread(stacktrace);
+ 
+     if (!crash_thread)
+-    {
+-        sr_gdb_stacktrace_free(stacktrace);
+         return NULL;
+-    }
+ 
+     struct sr_gdb_thread *optimized_thread
+         = sr_gdb_thread_get_optimized(crash_thread, stacktrace->libs,
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-elfutils-0.158.patch b/SOURCES/satyr-0.13-elfutils-0.158.patch
new file mode 100644
index 0000000..fafd6a1
--- /dev/null
+++ b/SOURCES/satyr-0.13-elfutils-0.158.patch
@@ -0,0 +1,26 @@
+From 8e872be02ea4f231347aa47f38b4418175c0e43d Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 8 Jan 2014 17:27:47 +0100
+Subject: [PATCH] unwind: fix build against elfutils-0.158
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/core_unwind.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index d0c7aec..7910254 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -206,7 +206,7 @@ open_coredump(const char *elf_file, const char *exe_file, char **error_msg)
+     ch->cb.section_address = dwfl_offline_section_address;
+     ch->dwfl = dwfl_begin(&ch->cb);
+ 
+-    if (dwfl_core_file_report(ch->dwfl, ch->eh) == -1)
++    if (dwfl_core_file_report(ch->dwfl, ch->eh, exe_file) == -1)
+     {
+         set_error_dwfl("dwfl_core_file_report");
+         goto fail_dwfl;
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-elfutils-unwinder.patch b/SOURCES/satyr-0.13-elfutils-unwinder.patch
new file mode 100644
index 0000000..690fd45
--- /dev/null
+++ b/SOURCES/satyr-0.13-elfutils-unwinder.patch
@@ -0,0 +1,260 @@
+From 17853e2316aed32d56343b2c4a73fa82b43a078d Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 21 Oct 2013 14:53:02 +0200
+Subject: [SATYR PATCH 2/3] unwind: Port to current API of elfutils unwinder
+
+Related to rhbz#1051569.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ configure.ac               |   2 +-
+ lib/core_unwind_elfutils.c | 164 +++++++++++++++++++++++++++++----------------
+ lib/internal_unwind.h      |   2 +-
+ 3 files changed, 110 insertions(+), 58 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index af6cb3f..cf3a193 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -104,7 +104,7 @@ AC_CHECK_LIB([elf], [main])
+ AC_CHECK_LIB([dw], [main])
+ AC_CHECK_LIB([dwfl], [main])
+ AC_CHECK_LIB([dl], [main])
+-AC_CHECK_FUNC(dwfl_frame_state_core, AC_DEFINE(HAVE_DWFL_FRAME_STATE_CORE, [], [Have function dwfl_frame_state_core for coredump unwinding]))
++AC_CHECK_FUNC(dwfl_getthreads, AC_DEFINE(HAVE_DWFL_NEXT_THREAD, [], [Have function dwfl_getthreads for coredump unwinding]))
+ 
+ # libunwind
+ AC_CHECK_HEADERS([libunwind-coredump.h])
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index 9a489ac..a8d8b3f 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -29,59 +29,108 @@
+ #include <stdio.h>
+ #include <string.h>
+ 
+-static struct sr_core_thread *
+-unwind_thread(Dwfl *dwfl, Dwfl_Frame_State *state, char **error_msg)
++struct frame_callback_arg
+ {
+-    struct sr_core_frame *head = NULL, *tail = NULL;
+-    pid_t tid = 0;
++    struct sr_core_thread *thread;
++    char *error_msg;
++};
+ 
+-    if (state)
++struct thread_callback_arg
++{
++    struct sr_core_stacktrace *stacktrace;
++    char *error_msg;
++};
++
++static int CB_STOP_UNWIND = DWARF_CB_ABORT+1;
++
++static int
++frame_callback(Dwfl_Frame *frame, void *data)
++{
++    struct frame_callback_arg *frame_arg = data;
++    char **error_msg = &frame_arg->error_msg;
++
++    Dwarf_Addr pc;
++    bool minus_one;
++    if (!dwfl_frame_pc(frame, &pc, &minus_one))
+     {
+-        tid = dwfl_frame_tid_get(state);
++        set_error_dwfl("dwfl_frame_pc");
++        return DWARF_CB_ABORT;
+     }
+ 
+-    while (state)
++    Dwfl *dwfl = dwfl_thread_dwfl(dwfl_frame_thread(frame));
++    struct sr_core_frame *result = resolve_frame(dwfl, pc, minus_one);
++
++    /* Do not unwind below __libc_start_main. */
++    if (0 == sr_strcmp0(result->function_name, "__libc_start_main"))
+     {
+-        Dwarf_Addr pc;
+-        bool minus_one;
+-        if (!dwfl_frame_state_pc(state, &pc, &minus_one))
+-        {
+-            warn("Failed to obtain PC: %s", dwfl_errmsg(-1));
+-            break;
+-        }
+-
+-        struct sr_core_frame *frame = resolve_frame(dwfl, pc, minus_one);
+-        list_append(head, tail, frame);
+-
+-        /* Do not unwind below __libc_start_main. */
+-        if (0 == sr_strcmp0(frame->function_name, "__libc_start_main"))
+-            break;
+-
+-        if (!dwfl_frame_unwind(&state))
+-        {
+-            warn("Cannot unwind frame: %s", dwfl_errmsg(-1));
+-            break;
+-        }
++        sr_core_frame_free(result);
++        return CB_STOP_UNWIND;
+     }
+ 
+-    if (!error_msg && !head)
++    frame_arg->thread->frames =
++        sr_core_frame_append(frame_arg->thread->frames, result);
++
++    return DWARF_CB_OK;
++}
++
++static int
++unwind_thread(Dwfl_Thread *thread, void *data)
++{
++    struct thread_callback_arg *thread_arg = data;
++    char **error_msg = &thread_arg->error_msg;
++
++    struct sr_core_thread *result = sr_core_thread_new();
++    if (!result)
+     {
+-        set_error("No frames found for thread id %d", (int)tid);
++        set_error("Failed to initialize stacktrace memory");
++        return DWARF_CB_ABORT;
+     }
++    result->id = (int64_t)dwfl_thread_tid(thread);
++
++    struct frame_callback_arg frame_arg =
++    {
++        .thread = result,
++        .error_msg = NULL
++    };
+ 
+-    struct sr_core_thread *thread = sr_core_thread_new();
+-    thread->id = (int64_t)tid;
+-    thread->frames = head;
+-    return thread;
++    int ret = dwfl_thread_getframes(thread, frame_callback, &frame_arg);
++    if (ret == -1)
++    {
++        warn("dwfl_thread_getframes failed for thread id %d: %s",
++             (int)result->id, dwfl_errmsg(-1));
++    }
++    else if (ret == DWARF_CB_ABORT)
++    {
++        *error_msg = frame_arg.error_msg;
++        goto abort;
++    }
++    else if (ret != 0 && ret != CB_STOP_UNWIND)
++    {
++        *error_msg = sr_strdup("Unknown error in dwfl_thread_getframes");
++        goto abort;
++    }
++
++    if (!error_msg && !frame_arg.thread->frames)
++    {
++        set_error("No frames found for thread id %d", (int)result->id);
++        goto abort;
++    }
++
++    thread_arg->stacktrace->threads =
++        sr_core_thread_append(thread_arg->stacktrace->threads, result);
++    return DWARF_CB_OK;
++
++abort:
++    sr_core_thread_free(result);
++    return DWARF_CB_ABORT;
+ }
+ 
+ struct sr_core_stacktrace *
+ sr_parse_coredump(const char *core_file,
+-                   const char *exe_file,
+-                   char **error_msg)
++                  const char *exe_file,
++                  char **error_msg)
+ {
+     struct sr_core_stacktrace *stacktrace = NULL;
+-    short signal = 0;
+ 
+     /* Initialize error_msg to 'no error'. */
+     if (error_msg)
+@@ -91,11 +140,9 @@ sr_parse_coredump(const char *core_file,
+     if (*error_msg)
+         return NULL;
+ 
+-    Dwfl_Frame_State *state = dwfl_frame_state_core(ch->dwfl, core_file);
+-    if (!state)
++    if (dwfl_core_file_attach(ch->dwfl, ch->eh) < 0)
+     {
+-        set_error("Failed to initialize frame state from core '%s'",
+-                   core_file);
++        set_error_dwfl("dwfl_core_file_attach");
+         goto fail_destroy_handle;
+     }
+ 
+@@ -105,20 +152,30 @@ sr_parse_coredump(const char *core_file,
+         set_error("Failed to initialize stacktrace memory");
+         goto fail_destroy_handle;
+     }
+-    struct sr_core_thread *threads_tail = NULL;
+ 
+-    do
++    struct thread_callback_arg thread_arg =
+     {
+-        struct sr_core_thread *t = unwind_thread(ch->dwfl, state, error_msg);
+-        if (*error_msg)
+-        {
+-            goto fail_destroy_trace;
+-        }
+-        list_append(stacktrace->threads, threads_tail, t);
+-        state = dwfl_frame_thread_next(state);
+-    } while (state);
++        .stacktrace = stacktrace,
++        .error_msg = NULL
++    };
+ 
+-    signal = get_signal_number(ch->eh, core_file);
++    int ret = dwfl_getthreads(ch->dwfl, unwind_thread, &thread_arg);
++    if (ret != 0)
++    {
++        if (ret == -1)
++            set_error_dwfl("dwfl_getthreads");
++        else if (ret == DWARF_CB_ABORT)
++            *error_msg = thread_arg.error_msg;
++        else
++            *error_msg = sr_strdup("Unknown error in dwfl_getthreads");
++
++        goto fail_destroy_trace;
++    }
++
++    stacktrace->executable = sr_strdup(exe_file);
++    stacktrace->signal = get_signal_number(ch->eh, core_file);
++    /* FIXME: is this the best we can do? */
++    stacktrace->crash_thread = stacktrace->threads;
+ 
+ fail_destroy_trace:
+     if (*error_msg)
+@@ -128,11 +185,6 @@ fail_destroy_trace:
+     }
+ fail_destroy_handle:
+     core_handle_free(ch);
+-
+-    stacktrace->executable = sr_strdup(exe_file);
+-    stacktrace->signal = signal;
+-    /* FIXME: is this the best we can do? */
+-    stacktrace->crash_thread = stacktrace->threads;
+     return stacktrace;
+ }
+ 
+diff --git a/lib/internal_unwind.h b/lib/internal_unwind.h
+index d537f86..2c9abb7 100644
+--- a/lib/internal_unwind.h
++++ b/lib/internal_unwind.h
+@@ -27,7 +27,7 @@
+ #include "config.h"
+ 
+ /* define macros indicating what unwinder are we using */
+-#if (defined HAVE_LIBELF_H && defined HAVE_GELF_H && defined HAVE_LIBELF && defined HAVE_LIBDW && defined HAVE_ELFUTILS_LIBDWFL_H && defined HAVE_DWFL_FRAME_STATE_CORE)
++#if (defined HAVE_LIBELF_H && defined HAVE_GELF_H && defined HAVE_LIBELF && defined HAVE_LIBDW && defined HAVE_ELFUTILS_LIBDWFL_H && defined HAVE_DWFL_NEXT_THREAD)
+ #  define WITH_LIBDWFL
+ #endif
+ 
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-fulfill-missing-values-in-core-frames.patch b/SOURCES/satyr-0.13-fulfill-missing-values-in-core-frames.patch
new file mode 100644
index 0000000..83ed2b6
--- /dev/null
+++ b/SOURCES/satyr-0.13-fulfill-missing-values-in-core-frames.patch
@@ -0,0 +1,67 @@
+From 90f464eb0c1ee4f5c23cb34ec6d199ae648625fe Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Fri, 16 May 2014 13:50:22 +0200
+Subject: [PATCH 1/5] Fulfill missing values in core/frames
+
+File name of __kernel_vsyscall function frame cannot be resolved but we
+known that the function comes from kernel.
+
+The frame often appears in backtraces of sleep.
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ lib/abrt.c | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+diff --git a/lib/abrt.c b/lib/abrt.c
+index 39bc45d..ed33800 100644
+--- a/lib/abrt.c
++++ b/lib/abrt.c
+@@ -24,6 +24,8 @@
+ #include "operating_system.h"
+ #include "core/unwind.h"
+ #include "core/stacktrace.h"
++#include "core/thread.h"
++#include "core/frame.h"
+ #include "core/fingerprint.h"
+ #include "python/stacktrace.h"
+ #include "koops/stacktrace.h"
+@@ -62,6 +64,26 @@ sr_abrt_print_report_from_dir(const char *directory,
+     return true;
+ }
+ 
++static void
++fulfill_missing_values(struct sr_core_stacktrace *core_stacktrace)
++{
++    struct sr_core_thread *thread = core_stacktrace->threads;
++    while (thread)
++    {
++        struct sr_core_frame *frame = thread->frames;
++        while (frame)
++        {
++            if (!frame->file_name && frame->function_name
++                && strcmp("__kernel_vsyscall", frame->function_name) == 0)
++            {
++                frame->file_name = sr_strdup("kernel");
++            }
++            frame = frame->next;
++        }
++        thread = thread->next;
++    }
++}
++
+ static bool
+ create_core_stacktrace(const char *directory, const char *gdb_output,
+                        bool hash_fingerprints, char **error_message)
+@@ -87,6 +109,8 @@ create_core_stacktrace(const char *directory, const char *gdb_output,
+     if (!core_stacktrace)
+         return false;
+ 
++    fulfill_missing_values(core_stacktrace);
++
+ #if 0
+     sr_core_fingerprint_generate(core_stacktrace,
+                                  error_message);
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-java-suppressed-exceptions.patch b/SOURCES/satyr-0.13-java-suppressed-exceptions.patch
new file mode 100644
index 0000000..63824de
--- /dev/null
+++ b/SOURCES/satyr-0.13-java-suppressed-exceptions.patch
@@ -0,0 +1,339 @@
+From 32085a78c063d80cc152f341343a87c8cfecfd2f Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Thu, 2 Oct 2014 16:46:30 +0200
+Subject: [SATYR PATCH] java: ignore suppressed exceptions
+
+Java exceptions can form a tree - every exception can have reference to
+exception that caused it and to a list of exceptions that were
+suppressed during handling[1]. We cannot take the suppressed exceptions
+into account without changing the uReport format, therefore this commit
+makes the java parser ignore them.
+
+[1] http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
+
+Fixes rhbz#1034857.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ include/utils.h                |   3 +
+ lib/java_frame.c               |  26 ++++--
+ lib/utils.c                    |  16 ++++
+ tests/java_stacktraces/java-04 | 185 +++++++++++++++++++++++++++++++++++++++++
+ tests/python/java.py           |  12 +++
+ 5 files changed, 234 insertions(+), 8 deletions(-)
+ create mode 100644 tests/java_stacktraces/java-04
+
+diff --git a/include/utils.h b/include/utils.h
+index 8d0a6ec..1c7984b 100644
+--- a/include/utils.h
++++ b/include/utils.h
+@@ -378,6 +378,9 @@ sr_skip_whitespace(const char *s);
+ char *
+ sr_skip_non_whitespace(const char *s);
+ 
++bool
++sr_skip_to_next_line_location(const char **s, int *line, int *column);
++
+ /**
+  * Emit a string of hex representation of bytes.
+  */
+diff --git a/lib/java_frame.c b/lib/java_frame.c
+index da9f26b..ee97572 100644
+--- a/lib/java_frame.c
++++ b/lib/java_frame.c
+@@ -28,6 +28,7 @@
+ #include "stacktrace.h"
+ #include "internal_utils.h"
+ #include <string.h>
++#include <ctype.h>
+ #include <inttypes.h>
+ 
+ #define SR_JF_MARK_NATIVE_METHOD "Native Method"
+@@ -337,6 +338,10 @@ sr_java_frame_parse_exception(const char **input,
+         if (strncmp("... ", cursor, strlen("... ")) == 0)
+             goto current_exception_done;
+ 
++        /* Suppressed exceptions follow after the end of current exception */
++        if (strncmp("Suppressed: ", cursor, strlen("Suppressed: ")) == 0)
++            goto current_exception_done;
++
+         /* The top most exception does not have '...' at its end */
+         if (strncmp("Caused by: ", cursor, strlen("Caused by: ")) == 0)
+             goto parse_inner_exception;
+@@ -363,19 +368,24 @@ sr_java_frame_parse_exception(const char **input,
+     goto exception_parsing_successful;
+ 
+ current_exception_done:
+-    sr_location_add(location, 0, sr_skip_char_cspan(&cursor, "\n"));
+-
+-    if (*cursor == '\n')
+-    {
+-        ++cursor;
+-        /* this adds one line */
+-        sr_location_add(location, 2, 0);
+-    }
++    sr_skip_to_next_line_location(&cursor, &location->line, &location->column);
+ 
+     mark = cursor;
+     cursor = sr_skip_whitespace(mark);
+     sr_location_add(location, 0, cursor - mark);
+ 
++    if (strncmp("Suppressed: ", cursor, strlen("Suppressed: ")) == 0)
++    {
++        /* Skip all lines related to the suppressed exception. We can do
++         * this by skipping all lines that begin with a whitespace - the
++         * main exception chain always begins without preceding whitespace.
++         */
++        sr_skip_to_next_line_location(&cursor, &location->line, &location->column);
++
++        while (cursor && isspace(*cursor))
++            sr_skip_to_next_line_location(&cursor, &location->line, &location->column);
++    }
++
+     if (strncmp("Caused by: ", cursor, strlen("Caused by: ")) == 0)
+     {
+ parse_inner_exception:
+diff --git a/lib/utils.c b/lib/utils.c
+index 3c036f3..fa3c0a0 100644
+--- a/lib/utils.c
++++ b/lib/utils.c
+@@ -656,6 +656,22 @@ sr_skip_non_whitespace(const char *s)
+     return (char *) s;
+ }
+ 
++bool
++sr_skip_to_next_line_location(const char **s, int *line, int *column)
++{
++    *column += sr_skip_char_cspan(s, "\n");
++
++    if (*s && **s == '\n')
++    {
++        *column = 0;
++        (*line)++;
++        (*s)++;
++        return true;
++    }
++
++    return false;
++}
++
+ char *
+ sr_bin2hex(char *dst, const char *str, int count)
+ {
+diff --git a/tests/java_stacktraces/java-04 b/tests/java_stacktraces/java-04
+new file mode 100644
+index 0000000..6968eb3
+--- /dev/null
++++ b/tests/java_stacktraces/java-04
+@@ -0,0 +1,185 @@
++Exception in thread "main" java.lang.RuntimeException: yes
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++	at WontCatchSuppressedException.main(WontCatchSuppressedException.java:35)
++Caused by: java.lang.RuntimeException: yes
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++	... 1 more
++	Suppressed: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++		at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++		... 1 more
++	Caused by: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++		... 3 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 3 more
++		Caused by: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++			... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 3 more
++		Caused by: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++			... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++	Caused by: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++		... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++	Suppressed: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++		at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++		... 1 more
++	Caused by: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++		... 3 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 3 more
++		Caused by: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++			... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 3 more
++		Caused by: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++			... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++			Suppressed: java.lang.RuntimeException: no
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++				at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++				at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++				... 5 more
++	Caused by: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++		... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++Caused by: java.lang.RuntimeException: yes
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++	... 2 more
++	Suppressed: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++		at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++		... 2 more
++	Caused by: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++		... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++	Suppressed: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:29)
++		at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++		... 2 more
++	Caused by: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++		... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++		Suppressed: java.lang.RuntimeException: no
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++			at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++			at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++			... 4 more
++Caused by: java.lang.RuntimeException: yes
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++	at WontCatchSuppressedException.die(WontCatchSuppressedException.java:27)
++	... 3 more
++	Suppressed: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++		at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++		... 3 more
++	Suppressed: java.lang.RuntimeException: no
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:23)
++		at WontCatchSuppressedException.close(WontCatchSuppressedException.java:16)
++		at WontCatchSuppressedException.die(WontCatchSuppressedException.java:28)
++		... 3 more
+diff --git a/tests/python/java.py b/tests/python/java.py
+index 3b85c18..1156fb6 100755
+--- a/tests/python/java.py
++++ b/tests/python/java.py
+@@ -152,6 +152,18 @@ class TestJavaStacktrace(BindingsTestCase):
+     def test_hash(self):
+         self.assertHashable(self.trace)
+ 
++    def test_suppressed(self):
++        contents = load_input_contents('../java_stacktraces/java-04')
++        trace = satyr.JavaStacktrace(contents)
++
++        names = 4*['java.lang.RuntimeException', 'WontCatchSuppressedException.die', 'WontCatchSuppressedException.die']
++        names[-1] = 'WontCatchSuppressedException.main'
++        msgs = 4*['yes', None, None]
++
++        for frame, name, msg in zip(trace.threads[0].frames, names, msgs):
++            self.assertEqual(frame.name, name)
++            self.assertEqual(frame.message, msg)
++
+ class TestJavaThread(BindingsTestCase):
+     def setUp(self):
+         self.thread = satyr.JavaStacktrace(contents).threads[0]
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-koops-unreliable-frames.patch b/SOURCES/satyr-0.13-koops-unreliable-frames.patch
new file mode 100644
index 0000000..a110e00
--- /dev/null
+++ b/SOURCES/satyr-0.13-koops-unreliable-frames.patch
@@ -0,0 +1,149 @@
+From fa978bdd2c3a576df357ce7905b11b73810dc100 Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Fri, 20 Jun 2014 20:42:00 +0200
+Subject: [SATYR PATCH 2/6] Fix the compat version of duphash generation
+
+sr_thread_get_duphash() must return NULL for oopses having no reliable
+frame.
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ lib/generic_thread.c      |  7 +++-
+ tests/koops_frame.at      |  1 -
+ tests/koops_stacktrace.at | 83 +++++++++++++++++++++++++++++++++++++++++++++++
+ tests/python/koops.py     |  1 +
+ 4 files changed, 90 insertions(+), 2 deletions(-)
+
+diff --git a/lib/generic_thread.c b/lib/generic_thread.c
+index b219f26..59fff1b 100644
+--- a/lib/generic_thread.c
++++ b/lib/generic_thread.c
+@@ -262,7 +262,12 @@ sr_thread_get_duphash(struct sr_thread *thread, int nframes, char *prefix,
+             nframes--;
+     }
+ 
+-    if (flags & SR_DUPHASH_NOHASH)
++    if ((flags & SR_DUPHASH_KOOPS_COMPAT) && strbuf->len == 0)
++    {
++        sr_strbuf_free(strbuf);
++        ret = NULL;
++    }
++    else if (flags & SR_DUPHASH_NOHASH)
+         ret = sr_strbuf_free_nobuf(strbuf);
+     else
+     {
+diff --git a/tests/koops_frame.at b/tests/koops_frame.at
+index be2cb11..4abc32a 100644
+--- a/tests/koops_frame.at
++++ b/tests/koops_frame.at
+@@ -288,4 +288,3 @@ main(void)
+   return 0;
+ }
+ ]])
+-
+diff --git a/tests/koops_stacktrace.at b/tests/koops_stacktrace.at
+index 404d53c..5e17ca7 100644
+--- a/tests/koops_stacktrace.at
++++ b/tests/koops_stacktrace.at
+@@ -730,3 +730,86 @@ main(void)
+   return 0;
+ }
+ ]])
++
++## --------------------- ##
++## sr_thread_get_duphash ##
++## --------------------- ##
++
++AT_TESTFUN([sr_thread_get_duphash],
++[[
++#include "koops/stacktrace.h"
++#include "koops/frame.h"
++#include "thread.h"
++#include "utils.h"
++#include <assert.h>
++#include <stdlib.h>
++#include <stdio.h>
++
++void generate_and_test(struct sr_thread *thread, int flags, const char *expected)
++{
++    char *hash = sr_thread_get_duphash(thread, 1, NULL, flags);
++
++    if (NULL == hash && NULL != expected)
++    {
++        fprintf(stderr, "'%s' != NULL\n", expected);
++        assert(!"NULL was NOT expected");
++    }
++
++    if (NULL != hash && NULL == expected)
++    {
++        fprintf(stderr, "NULL != '%s'\n", hash);
++        assert(!"NULL was expected");
++    }
++
++    if (NULL == hash && NULL == expected)
++        return;
++
++    if (strcmp(hash, expected) != 0)
++    {
++        fprintf(stderr, "'%s' != '%s'\n", expected, hash);
++        //assert(!"Expected and hash are not equal");
++    }
++
++    free(hash);
++}
++
++void test(struct sr_thread *thread, const char *expected, const char *expected_compat)
++{
++    generate_and_test(thread, SR_DUPHASH_NOHASH, expected);
++    fprintf(stderr, "COMPAT\n");
++    generate_and_test(thread, SR_DUPHASH_NOHASH|SR_DUPHASH_KOOPS_COMPAT, expected_compat);
++}
++
++int main(void)
++{
++    struct sr_koops_stacktrace *stacktrace = sr_koops_stacktrace_new();
++    struct sr_thread *thread = (struct sr_thread *)stacktrace;
++
++    struct sr_koops_frame *frame = sr_koops_frame_new();
++    stacktrace->frames = frame;
++
++    frame->address = 0xDEADBEAF;
++
++    fprintf(stderr, "Checkpoint 1\n");
++    frame->reliable = 1;
++    test(thread, "Thread\n0xdeadbeaf\n", "0xdeadbeaf\n");
++
++    fprintf(stderr, "Checkpoint 2\n");
++    frame->reliable = 0;
++    test(thread, "Thread\n0xdeadbeaf\n", NULL);
++
++    frame->address = 0xDEADBEAF;
++    frame->function_name = sr_strdup("omg_warn_slowpath_common");
++    stacktrace->frames = frame;
++
++    fprintf(stderr, "Checkpoint 3\n");
++    frame->reliable = 1;
++    test(thread, "Thread\nomg_warn_slowpath_common\n", "omg_warn_slowpath_common\n");
++
++    fprintf(stderr, "Checkpoint 4\n");
++    frame->reliable = 0;
++    test(thread, "Thread\nomg_warn_slowpath_common\n", NULL);
++
++    return 0;
++}
++]])
+diff --git a/tests/python/koops.py b/tests/python/koops.py
+index 3f8fe57..0686b70 100755
+--- a/tests/python/koops.py
++++ b/tests/python/koops.py
+@@ -75,6 +75,7 @@ class TestKerneloops(BindingsTestCase):
+                          compat_expected_plain)
+         self.assertEqual(self.koops.get_duphash(flags=satyr.DUPHASH_KOOPS_COMPAT, frames=6),
+                          '5718b3a86c64e7bed5e8ead08ae3084e447ddbee')
++        self.assertRaises(RuntimeError, self.koops.get_duphash, flags=(satyr.DUPHASH_NOHASH|satyr.DUPHASH_KOOPS_COMPAT), frames=-1)
+ 
+     def test_crash_thread(self):
+         self.assertTrue(self.koops.crash_thread is self.koops)
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-limit-stacktrace-depth.patch b/SOURCES/satyr-0.13-limit-stacktrace-depth.patch
new file mode 100644
index 0000000..4015dbf
--- /dev/null
+++ b/SOURCES/satyr-0.13-limit-stacktrace-depth.patch
@@ -0,0 +1,66 @@
+From 1c223116114ddf80609e79ec7ada0ec4a5a463f3 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 27 Aug 2014 12:51:41 +0200
+Subject: [SATYR PATCH 5/6] unwind: limit the number of frames unwound
+
+Fixes rhbz#1133907.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/core_unwind_elfutils.c | 15 +++++++++++++--
+ 1 file changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index 6b904c7..43d66be 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -29,10 +29,13 @@
+ #include <stdio.h>
+ #include <string.h>
+ 
++#define FRAME_LIMIT 1024
++
+ struct frame_callback_arg
+ {
+     struct sr_core_thread *thread;
+     char *error_msg;
++    unsigned nframes;
+ };
+ 
+ struct thread_callback_arg
+@@ -41,7 +44,7 @@ struct thread_callback_arg
+     char *error_msg;
+ };
+ 
+-static int CB_STOP_UNWIND = DWARF_CB_ABORT+1;
++static const int CB_STOP_UNWIND = DWARF_CB_ABORT+1;
+ 
+ static int
+ frame_callback(Dwfl_Frame *frame, void *data)
+@@ -70,6 +73,13 @@ frame_callback(Dwfl_Frame *frame, void *data)
+     frame_arg->thread->frames =
+         sr_core_frame_append(frame_arg->thread->frames, result);
+ 
++    /* Avoid huge stacktraces from programs stuck in infinite recursion. */
++    frame_arg->nframes++;
++    if (frame_arg->nframes >= FRAME_LIMIT)
++    {
++        return CB_STOP_UNWIND;
++    }
++
+     return DWARF_CB_OK;
+ }
+ 
+@@ -90,7 +100,8 @@ unwind_thread(Dwfl_Thread *thread, void *data)
+     struct frame_callback_arg frame_arg =
+     {
+         .thread = result,
+-        .error_msg = NULL
++        .error_msg = NULL,
++        .nframes = 0
+     };
+ 
+     int ret = dwfl_thread_getframes(thread, frame_callback, &frame_arg);
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-normalization-actualize-list-of-functions.patch b/SOURCES/satyr-0.13-normalization-actualize-list-of-functions.patch
new file mode 100644
index 0000000..6ff08da
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-actualize-list-of-functions.patch
@@ -0,0 +1,188 @@
+From 45973b73fd538eb194c0ddc52457eb29b06dd84f Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Tue, 10 May 2016 15:13:53 +0200
+Subject: [PATCH] normalization: actualize list of functions
+
+Related to #1332869
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ lib/normalize.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 99 insertions(+), 3 deletions(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index e3a7a13..9d7574e 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -89,6 +89,7 @@ is_removable_dbus(const char *function_name,
+         call_match(function_name, source_file, "gerror_to_dbus_error_message", "dbus-gobject.c", NULL) ||
+         call_match(function_name, source_file, "dbus_g_method_return_error", "dbus-gobject.c", NULL) ||
+         call_match(function_name, source_file, "message_queue_dispatch", "dbus-gmain.c", NULL) ||
++        call_match(function_name, source_file, "_dbus_abort", "dbus-sysdeps.c", "libdbus", NULL) ||
+         call_match(function_name, source_file, "dbus_connection_dispatch", "dbus-connection.c", "libdbus", NULL);
+ }
+ 
+@@ -134,7 +135,17 @@ is_removable_glib(const char *function_name,
+         call_match(function_name, source_file, "g_thread_pool_thread_proxy", "gthreadpool.c", "libglib", NULL) ||
+         call_match(function_name, source_file, "g_thread_create_proxy", "gthread.c", "libglib", NULL) ||
+         call_match(function_name, source_file, "g_cclosure_marshal_VOID__BOXED", "gmarshal.c", "libgobject", NULL) ||
+-        call_match(function_name, source_file, "g_cclosure_marshal_VOID__VOID", "gclosure.c", "gmarshal.c", "libgobject", NULL);
++        call_match(function_name, source_file, "g_cclosure_marshal_VOID__VOID", "gclosure.c", "gmarshal.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "g_object_notify", "gobject.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "Glib::exception_handlers_invoke()", "libglibmm", NULL) ||
++        call_match(function_name, source_file, "g_signal_handlers_destroy", "gsignal.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "g_vasprintf", "gprintf.c", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_strdup_vprintf", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_strdup_printf", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_print", "libglib", NULL) ||
++        call_match(function_name, source_file, "invalid_closure_notify", "gsignal.c", "libgobject", NULL) ||
++        call_match(function_name, source_file, "smc_tree_abort", "gslice.c", "libglib", NULL) ||
++        call_match(function_name, source_file, "g_thread_abort", "libglib", NULL);
+ }
+ 
+ static bool
+@@ -147,6 +158,7 @@ is_removable_libstdcpp(const char *function_name,
+         call_match(function_name, source_file, "std::terminate", "eh_terminate.cc", NULL) ||
+         call_match(function_name, source_file, "__cxxabiv1::__cxa_throw", "eh_throw.cc", NULL) ||
+         call_match(function_name, source_file, "__cxxabiv1::__cxa_rethrow", "eh_throw.cc", NULL) ||
++        call_match(function_name, source_file, "__verbose_terminate_handler", "vterminate.cc", NULL) ||
+         call_match(function_name, source_file, "__cxxabiv1::__cxa_pure_virtual", "pure.cc", NULL);
+ }
+ 
+@@ -173,6 +185,8 @@ is_removable_xorg(const char *function_name,
+         call_match(function_name, source_file, "ddxGiveUp", "xf86Init.c", NULL) ||
+         call_match(function_name, source_file, "OsAbort", "utils.c", NULL) ||
+         call_match(function_name, source_file, "handle_error", "xcb_io.c", "libX11", NULL) ||
++        call_match(function_name, source_file, "_XIOError", "XlibInt.c", "libX11", NULL) ||
++        call_match(function_name, source_file, "_XEventsQueued", "xcb_io.c", "libX11", NULL) ||
+         call_match(function_name, source_file, "handle_response", "xcb_io.c", "libX11", NULL);
+ }
+ 
+@@ -184,10 +198,82 @@ is_removable_glibc(const char *function_name,
+         call_match(function_name, source_file, "_start", "", NULL) ||
+         call_match(function_name, source_file, "__libc_start_main", "libc", NULL) ||
+         call_match(function_name, source_file, "clone", "clone.S", "libc", NULL) ||
++        call_match(function_name, source_file, "poll", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_new_fclose", "iofclose.c", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_vfprintf_internal", "vfprintf.c", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_default_xsputn", "genops.c", "libc", NULL) ||
++        call_match(function_name, source_file, "_IO_wdefault_xsputn", "wgenops.c", "libc", NULL) ||
++        call_match(function_name, source_file, "__libc_message", "libc_fatal.c", "libc", NULL) ||
+         call_match(function_name, source_file, "start_thread", "pthread_create.c", "libpthread", NULL);
+ }
+ 
+ static bool
++is_removable_other(const char *function_name,
++                   const char *source_file)
++{
++    return
++        call_match(function_name, source_file, "assert_cursor", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "assert_device_not_suspended", "intel_uncore.c", NULL) ||
++        call_match(function_name, source_file, "assert_pipe", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "assert_plane", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "assert_transcoder_disabled", "intel_display.c", NULL) ||
++        call_match(function_name, source_file, "btrfs_assert_delayed_root_empty", "delayed-inode.c", "btrfs", NULL) ||
++        call_match(function_name, source_file, "_cogl_set_error", "cogl-error.c", "libcogl", NULL) ||
++        call_match(function_name, source_file, "defaultCrashHandler", "kcrash.cpp", "libKF5Crash", NULL) ||
++        call_match(function_name, source_file, "_dl_signal_error", "dl-error.c", "ld-linux", NULL) ||
++        call_match(function_name, source_file, "error_dialog_response_cb", "", NULL) ||
++        call_match(function_name, source_file, "do_warn", "_warnings.c", "libpython",NULL) ||
++        call_match(function_name, source_file, "QMessageLogger::fatal(char const*, ...) const", "", NULL) ||
++        call_match(function_name, source_file, "nsProfileLock::FatalSignalHandler(int, siginfo_t*, void*)", "", NULL) ||
++        call_match(function_name, source_file, "qt_message_output", "qglobal.cpp", "libQtCore", NULL) ||
++        call_match(function_name, source_file, "qt_message_output(QtMsgType, char const*)", "", NULL) ||
++        call_match(function_name, source_file, "signalHandler(int, siginfo_t*, void*)", "", NULL) ||
++        call_match(function_name, source_file, "FatalSignalHandler", "nsProfileLock.cpp", "libxul", NULL) ||
++        call_match(function_name, source_file, "Foam::error::abort()", "", NULL) ||
++        call_match(function_name, source_file, "JS_AbortIfWrongThread", "libmozjs", NULL) ||
++        call_match(function_name, source_file, "Crash::defaultCrashHandler(int)", "libkdeui", "libKF5Crash", NULL) ||
++        call_match(function_name, source_file, "Py_FatalError", "pythonrun.c", "libpython", NULL) ||
++        call_match(function_name, source_file, "__btrfs_abort_transaction", "btrfs", NULL) ||
++        call_match(function_name, source_file, "assert_pch_hdmi_disabled", "", NULL) ||
++        call_match(function_name, source_file, "assert_pll", "", NULL) ||
++        call_match(function_name, source_file, "core::system::abort()", "", NULL) ||
++        call_match(function_name, source_file, "ddd_assert_fail", "assert.C", NULL) ||
++        call_match(function_name, source_file, "debug_dma_assert_idle", "", NULL) ||
++        call_match(function_name, source_file, "error_handler", "", NULL) ||
++        call_match(function_name, source_file, "fatal_error_signal", "", NULL) ||
++        call_match(function_name, source_file, "fatal_handler", "signal.c", "libfreerdp", NULL) ||
++        call_match(function_name, source_file, "gpf_notice", "", NULL) ||
++        call_match(function_name, source_file, "log", "", NULL) ||
++        call_match(function_name, source_file, "_log", "", NULL) ||
++        call_match(function_name, source_file, "log_assert_failed", "", NULL) ||
++        call_match(function_name, source_file, "mozalloc_abort", "mozalloc_abort.cpp", "libmozalloc", NULL) ||
++        call_match(function_name, source_file, "mozalloc_abort(char const*)", "libmozalloc", "content-container", "plugin-container", NULL) ||
++        call_match(function_name, source_file, "note_interrupt", "spurious.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "print_bad_pte", "memory.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "print_oops_end_marker", "panic.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "printk", "printk.c", "vmlinux", NULL) ||
++        call_match(function_name, source_file, "qupzilla_signal_handler", "main.cpp", "qupzilla", NULL) ||
++        call_match(function_name, source_file, "rb_bug", "error.c", "libruby", NULL) ||
++        call_match(function_name, source_file, "sighandler", "", NULL) ||
++        call_match(function_name, source_file, "signalHandler(int)", "", NULL) ||
++        call_match(function_name, source_file, "signal_abort", "signal.c", NULL) ||
++        call_match(function_name, source_file, "signal_handler", "", NULL) ||
++        call_match(function_name, source_file, "sys_abort", "error.c", "libgfortran", NULL) ||
++        call_match(function_name, source_file, "terminate_due_to_signal", "emacs.c", "emacs", NULL) ||
++        call_match(function_name, source_file, "wl_log", "wayland-util.c", NULL) ||
++        call_match(function_name, source_file, "display_protocol_error", "wayland-client.c", NULL) ||
++        call_match(function_name, source_file, "display_handle_error", "wayland-client.c", NULL) ||
++        call_match(function_name, source_file, "x_io_error", "libmutter", NULL) ||
++        call_match(function_name, source_file, "__ioremap_calle ", "ioremap.c", NULL) ||
++        call_match(function_name, source_file, "ioremap_nocache", "ioremap.c", NULL) ||
++        call_match(function_name, source_file, "wpa_msg", "wpa_debug.c", NULL) ||
++        call_match(function_name, source_file, "js::gc::FinalizeArenas(js::FreeOp*, js::gc::ArenaHeader**, js::gc::ArenaList&, js::gc::AllocKind, js::SliceBudget&)\"js::Shape::finalize(js::FreeOp*)", "", NULL) ||
++        call_match(function_name, source_file, "WTF::StringImpl::endsWith(char const*, unsigned int, bool) const", "", NULL) ||
++        call_match(function_name, source_file, "mozilla::plugins::child::_invokedefault(_NPP*, NPObject*, _NPVariant const*, unsigned int, _NPVariant*)", "", NULL) ||
++        call_match(function_name, source_file, "xitk_signal_handler", "xitk.c", "xine", NULL);
++}
++
++static bool
+ is_removable_glibc_with_above(const char *function_name,
+                               const char *source_file)
+ {
+@@ -205,6 +291,13 @@ is_removable_glibc_with_above(const char *function_name,
+         call_match(function_name, source_file, "__snprintf_chk", "", NULL) ||
+         call_match(function_name, source_file, "___snprintf_chk", "", NULL) ||
+         call_match(function_name, source_file, "__vasprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__vsprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "___sprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__fwprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__asprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "___printf_chk", "", NULL) ||
++        call_match(function_name, source_file, "___fprintf_chk", "", NULL) ||
++        call_match(function_name, source_file, "__vswprintf_chk", "", NULL) ||
+         call_match(function_name, source_file, "malloc_consolidate", "malloc.c", "libc", NULL) ||
+         call_match(function_name, source_file, "malloc_printerr", "malloc.c", "libc", NULL) ||
+         call_match(function_name, source_file, "_int_malloc", "malloc.c", "libc", NULL) ||
+@@ -295,6 +388,7 @@ sr_gdb_is_exit_frame(struct sr_gdb_frame *frame)
+         /* Terminates a function in case of buffer overflow. */
+         sr_gdb_frame_calls_func(frame, "__chk_fail", "chk_fail.c", "libc.so", NULL) ||
+         sr_gdb_frame_calls_func(frame, "__stack_chk_fail", "stack_chk_fail.c", "libc.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "do_exit", "exit.c", NULL) ||
+         sr_gdb_frame_calls_func(frame, "kill", "syscall-template.S", NULL);
+ }
+ 
+@@ -365,7 +459,8 @@ sr_normalize_gdb_thread(struct sr_gdb_thread *thread)
+             is_removable_linux(frame->function_name, frame->source_file) ||
+             is_removable_xorg(frame->function_name, frame->source_file) ||
+             is_removable_jvm(frame->function_name, frame->source_file) ||
+-            is_removable_vim(frame->function_name, frame->source_file);
++            is_removable_vim(frame->function_name, frame->source_file) ||
++            is_removable_other(frame->function_name, frame->source_file);
+ 
+         bool removable_with_above =
+             is_removable_glibc_with_above(frame->function_name, frame->source_file) ||
+@@ -510,7 +605,8 @@ sr_normalize_core_thread(struct sr_core_thread *thread)
+             is_removable_linux(frame->function_name, frame->file_name) ||
+             is_removable_xorg(frame->function_name, frame->file_name) ||
+             is_removable_jvm(frame->function_name, frame->file_name) ||
+-            is_removable_vim(frame->function_name, frame->file_name);
++            is_removable_vim(frame->function_name, frame->file_name) ||
++            is_removable_other(frame->function_name, frame->file_name);
+ 
+         bool removable_with_above =
+             is_removable_glibc_with_above(frame->function_name, frame->file_name)  ||
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-add-glibc-__assert_fail_base.patch b/SOURCES/satyr-0.13-normalization-add-glibc-__assert_fail_base.patch
new file mode 100644
index 0000000..7c7c505
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-add-glibc-__assert_fail_base.patch
@@ -0,0 +1,24 @@
+From ae3890bb65dfd13c4dd1a52b2f9fb1d1a3515253 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Bry=C5=A1a?= <mbrysa@redhat.com>
+Date: Wed, 27 May 2015 13:56:12 +0200
+Subject: [PATCH] normalization: add glibc __assert_fail_base
+
+---
+ lib/normalize.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index 0e9b497..4b22645 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -193,6 +193,7 @@ is_removable_glibc_with_above(const char *function_name,
+ {
+     return
+         call_match(function_name, source_file, "__assert_fail", "", NULL) ||
++        call_match(function_name, source_file, "__assert_fail_base", "", NULL) ||
+         call_match(function_name, source_file, "__chk_fail", "", NULL) ||
+         call_match(function_name, source_file, "__longjmp_chk", "", NULL) ||
+         call_match(function_name, source_file, "__malloc_assert", "", NULL) ||
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-add-glibc-__libc_fatal.patch b/SOURCES/satyr-0.13-normalization-add-glibc-__libc_fatal.patch
new file mode 100644
index 0000000..b24b413
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-add-glibc-__libc_fatal.patch
@@ -0,0 +1,26 @@
+From 933bd99829000bcd9bf674f0e2e61c562767a09b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Bry=C5=A1a?= <mbrysa@redhat.com>
+Date: Wed, 27 May 2015 14:37:15 +0200
+Subject: [PATCH] normalization: add glibc __libc_fatal
+
+---
+ lib/normalize.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index 4b22645..bec2ccc 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -216,7 +216,8 @@ is_removable_glibc_with_above(const char *function_name,
+         call_match(function_name, source_file, "__libc_memalign", "malloc.c", NULL) ||
+         call_match(function_name, source_file, "__libc_realloc", "malloc.c", NULL) ||
+         call_match(function_name, source_file, "__posix_memalign", "malloc.c", NULL) ||
+-        call_match(function_name, source_file, "__libc_calloc", "malloc.c", NULL);
++        call_match(function_name, source_file, "__libc_calloc", "malloc.c", NULL) ||
++        call_match(function_name, source_file, "__libc_fatal", "libc", NULL);
+ }
+ 
+ static char *
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-additional-X-GDK-functions.patch b/SOURCES/satyr-0.13-normalization-additional-X-GDK-functions.patch
new file mode 100644
index 0000000..74eba7d
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-additional-X-GDK-functions.patch
@@ -0,0 +1,40 @@
+From e436cd0b6eb389e6803b8b519e936429e694e453 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Fri, 28 Nov 2014 17:59:45 +0100
+Subject: [PATCH] normalization: additional X/GDK functions
+
+Closes #199.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/normalize.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index 81be188..0e9b497 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -100,7 +100,8 @@ is_removable_gdk(const char *function_name,
+         call_match(function_name, source_file, "gdk_x_error", "gdkmain-x11.c", NULL) ||
+         call_match(function_name, source_file, "gdk_threads_dispatch", "gdk.c", NULL) ||
+         call_match(function_name, source_file, "gdk_event_dispatch", "gdkevents-x11.c", "gdkevents.c", NULL) ||
+-        call_match(function_name, source_file, "gdk_event_source_dispatch", "gdkeventsource.c", NULL);
++        call_match(function_name, source_file, "gdk_event_source_dispatch", "gdkeventsource.c", NULL) ||
++        call_match(function_name, source_file, "_gdk_x11_display_error_event", "gdkdisplay-x11.c", "libgdk", NULL);
+ }
+ 
+ static bool
+@@ -170,7 +171,9 @@ is_removable_xorg(const char *function_name,
+         call_match(function_name, source_file, "AbortServer", "log.c", NULL) ||
+         call_match(function_name, source_file, "AbortDDX", "xf86Init.c", NULL) ||
+         call_match(function_name, source_file, "ddxGiveUp", "xf86Init.c", NULL) ||
+-        call_match(function_name, source_file, "OsAbort", "utils.c", NULL);
++        call_match(function_name, source_file, "OsAbort", "utils.c", NULL) ||
++        call_match(function_name, source_file, "handle_error", "xcb_io.c", "libX11", NULL) ||
++        call_match(function_name, source_file, "handle_response", "xcb_io.c", "libX11", NULL);
+ }
+ 
+ static bool
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalization-normalize-out-exit-frames.patch b/SOURCES/satyr-0.13-normalization-normalize-out-exit-frames.patch
new file mode 100644
index 0000000..c4d57e3
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalization-normalize-out-exit-frames.patch
@@ -0,0 +1,144 @@
+From b4120f370475bde243a1ae5bde64f4f0a641c3c3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Marek=20Bry=C5=A1a?= <mbrysa@redhat.com>
+Date: Wed, 27 May 2015 18:43:52 +0200
+Subject: [PATCH] normalization: normalize out exit frames
+
+---
+ include/core/thread.h |  3 +++
+ lib/core_thread.c     | 30 +++++++++++++++++-------------
+ lib/normalize.c       | 36 +++++++++++++++++++++---------------
+ 3 files changed, 41 insertions(+), 28 deletions(-)
+
+diff --git a/include/core/thread.h b/include/core/thread.h
+index 0ba71e4..d6125df 100644
+--- a/include/core/thread.h
++++ b/include/core/thread.h
+@@ -123,6 +123,9 @@ struct sr_core_thread *
+ sr_core_thread_append(struct sr_core_thread *dest,
+                       struct sr_core_thread *item);
+ 
++bool
++sr_core_thread_is_exit_frame(struct sr_core_frame *frame);
++
+ struct sr_core_frame *
+ sr_core_thread_find_exit_frame(struct sr_core_thread *thread);
+ 
+diff --git a/lib/core_thread.c b/lib/core_thread.c
+index 54fb89c..d8aa943 100644
+--- a/lib/core_thread.c
++++ b/lib/core_thread.c
+@@ -149,6 +149,22 @@ sr_core_thread_append(struct sr_core_thread *dest,
+     return dest;
+ }
+ 
++bool
++sr_core_thread_is_exit_frame(struct sr_core_frame *frame)
++{
++    return
++        sr_core_frame_calls_func(frame, "__run_exit_handlers", NULL) ||
++        sr_core_frame_calls_func(frame, "raise", "libc.so", "libc-", "libpthread.so", NULL) ||
++        sr_core_frame_calls_func(frame, "__GI_raise", NULL) ||
++        sr_core_frame_calls_func(frame, "exit", NULL) ||
++        sr_core_frame_calls_func(frame, "abort", "libc.so", "libc-", NULL) ||
++        sr_core_frame_calls_func(frame, "__GI_abort", NULL) ||
++        /* Terminates a function in case of buffer overflow. */
++        sr_core_frame_calls_func(frame, "__chk_fail", "libc.so", NULL) ||
++        sr_core_frame_calls_func(frame, "__stack_chk_fail", "libc.so", NULL) ||
++        sr_core_frame_calls_func(frame, "kill", NULL);
++}
++
+ struct sr_core_frame *
+ sr_core_thread_find_exit_frame(struct sr_core_thread *thread)
+ {
+@@ -156,19 +172,7 @@ sr_core_thread_find_exit_frame(struct sr_core_thread *thread)
+     struct sr_core_frame *result = NULL;
+     while (frame)
+     {
+-        bool is_exit_frame =
+-            sr_core_frame_calls_func(frame, "__run_exit_handlers", NULL) ||
+-            sr_core_frame_calls_func(frame, "raise", "libc.so", "libc-", "libpthread.so", NULL) ||
+-            sr_core_frame_calls_func(frame, "__GI_raise", NULL) ||
+-            sr_core_frame_calls_func(frame, "exit", NULL) ||
+-            sr_core_frame_calls_func(frame, "abort", "libc.so", "libc-", NULL) ||
+-            sr_core_frame_calls_func(frame, "__GI_abort", NULL) ||
+-            /* Terminates a function in case of buffer overflow. */
+-            sr_core_frame_calls_func(frame, "__chk_fail", "libc.so", NULL) ||
+-            sr_core_frame_calls_func(frame, "__stack_chk_fail", "libc.so", NULL) ||
+-            sr_core_frame_calls_func(frame, "kill", NULL);
+-
+-        if (is_exit_frame)
++        if (sr_core_thread_is_exit_frame(frame))
+             result = frame;
+ 
+         frame = frame->next;
+diff --git a/lib/normalize.c b/lib/normalize.c
+index bec2ccc..e3a7a13 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -282,6 +282,22 @@ remove_func_prefix(char *function_name, const char *prefix, int num)
+     memmove(function_name, function_name + num, func_len - num + 1);
+ }
+ 
++static bool
++sr_gdb_is_exit_frame(struct sr_gdb_frame *frame)
++{
++    return
++        sr_gdb_frame_calls_func(frame, "__run_exit_handlers", "exit.c", NULL) ||
++        sr_gdb_frame_calls_func(frame, "raise", "pt-raise.c", "libc.so", "libc-", "libpthread.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "__GI_raise", "raise.c", NULL) ||
++        sr_gdb_frame_calls_func(frame, "exit", "exit.c", NULL) ||
++        sr_gdb_frame_calls_func(frame, "abort", "abort.c", "libc.so", "libc-", NULL) ||
++        sr_gdb_frame_calls_func(frame, "__GI_abort", "abort.c", NULL) ||
++        /* Terminates a function in case of buffer overflow. */
++        sr_gdb_frame_calls_func(frame, "__chk_fail", "chk_fail.c", "libc.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "__stack_chk_fail", "stack_chk_fail.c", "libc.so", NULL) ||
++        sr_gdb_frame_calls_func(frame, "kill", "syscall-template.S", NULL);
++}
++
+ void
+ sr_normalize_gdb_thread(struct sr_gdb_thread *thread)
+ {
+@@ -352,7 +368,8 @@ sr_normalize_gdb_thread(struct sr_gdb_thread *thread)
+             is_removable_vim(frame->function_name, frame->source_file);
+ 
+         bool removable_with_above =
+-            is_removable_glibc_with_above(frame->function_name, frame->source_file);
++            is_removable_glibc_with_above(frame->function_name, frame->source_file) ||
++            sr_gdb_is_exit_frame(frame);
+ 
+         if (removable_with_above)
+         {
+@@ -496,7 +513,8 @@ sr_normalize_core_thread(struct sr_core_thread *thread)
+             is_removable_vim(frame->function_name, frame->file_name);
+ 
+         bool removable_with_above =
+-            is_removable_glibc_with_above(frame->function_name, frame->file_name);
++            is_removable_glibc_with_above(frame->function_name, frame->file_name)  ||
++            sr_core_thread_is_exit_frame(frame);
+ 
+         if (removable_with_above)
+         {
+@@ -689,19 +707,7 @@ sr_glibc_thread_find_exit_frame(struct sr_gdb_thread *thread)
+     struct sr_gdb_frame *result = NULL;
+     while (frame)
+     {
+-        bool is_exit_frame =
+-            sr_gdb_frame_calls_func(frame, "__run_exit_handlers", "exit.c", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "raise", "pt-raise.c", "libc.so", "libc-", "libpthread.so", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "__GI_raise", "raise.c", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "exit", "exit.c", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "abort", "abort.c", "libc.so", "libc-", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "__GI_abort", "abort.c", NULL) ||
+-            /* Terminates a function in case of buffer overflow. */
+-            sr_gdb_frame_calls_func(frame, "__chk_fail", "chk_fail.c", "libc.so", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "__stack_chk_fail", "stack_chk_fail.c", "libc.so", NULL) ||
+-            sr_gdb_frame_calls_func(frame, "kill", "syscall-template.S", NULL);
+-
+-        if (is_exit_frame)
++        if (sr_gdb_is_exit_frame(frame))
+             result = frame;
+ 
+         frame = frame->next;
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-normalize-extend-xorg-blacklist.patch b/SOURCES/satyr-0.13-normalize-extend-xorg-blacklist.patch
new file mode 100644
index 0000000..e747010
--- /dev/null
+++ b/SOURCES/satyr-0.13-normalize-extend-xorg-blacklist.patch
@@ -0,0 +1,34 @@
+From f3f36ff858f50aed5a6c4df5144ae15aeb5255b9 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 24 Feb 2014 12:44:12 +0100
+Subject: [PATCH] normalize: extend xorg blacklist
+
+Closes rhbz#1068767.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/normalize.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/lib/normalize.c b/lib/normalize.c
+index d332882..81be188 100644
+--- a/lib/normalize.c
++++ b/lib/normalize.c
+@@ -164,7 +164,13 @@ is_removable_xorg(const char *function_name,
+         call_match(function_name, source_file, "_XReply", "xcb_io.c", NULL) ||
+         call_match(function_name, source_file, "_XError", "XlibInt.c", NULL) ||
+         call_match(function_name, source_file, "XSync", "Sync.c", NULL) ||
+-        call_match(function_name, source_file, "process_responses", "xcb_io.c", NULL);
++        call_match(function_name, source_file, "process_responses", "xcb_io.c", NULL) ||
++        call_match(function_name, source_file, "OsSigHandler", "osinit.c", NULL) ||
++        call_match(function_name, source_file, "FatalError", "log.c", NULL) ||
++        call_match(function_name, source_file, "AbortServer", "log.c", NULL) ||
++        call_match(function_name, source_file, "AbortDDX", "xf86Init.c", NULL) ||
++        call_match(function_name, source_file, "ddxGiveUp", "xf86Init.c", NULL) ||
++        call_match(function_name, source_file, "OsAbort", "utils.c", NULL);
+ }
+ 
+ static bool
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-os-add-support-for-OS-Variant.patch b/SOURCES/satyr-0.13-os-add-support-for-OS-Variant.patch
new file mode 100644
index 0000000..1456ada
--- /dev/null
+++ b/SOURCES/satyr-0.13-os-add-support-for-OS-Variant.patch
@@ -0,0 +1,166 @@
+From 20cafc9b23448aaab4fd8f07a5a1defd0cd7038a Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Tue, 1 Sep 2015 13:05:48 +0200
+Subject: [PATCH] os: add support for OS Variant
+
+VARIANT= and VARIANT_ID= fields are optional.
+
+http://www.freedesktop.org/software/systemd/man/os-release.html
+
+Related to abrt/abrt#995
+Resolves: #1342469
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+
+Conflicts:
+	include/operating_system.h
+	lib/operating_system.c
+---
+ include/operating_system.h |  1 +
+ lib/operating_system.c     | 16 ++++++++++++++++
+ tests/operating_system.at  | 43 +++++++++++++++++++++++++++++++++++++++----
+ 3 files changed, 56 insertions(+), 4 deletions(-)
+
+diff --git a/include/operating_system.h b/include/operating_system.h
+index f26e322..84fcce0 100644
+--- a/include/operating_system.h
++++ b/include/operating_system.h
+@@ -37,6 +37,7 @@ struct sr_operating_system
+     char *cpe;
+     /* Uptime in seconds. */
+     uint64_t uptime;
++    char *variant;
+ };
+ 
+ struct sr_operating_system *
+diff --git a/lib/operating_system.c b/lib/operating_system.c
+index 64f7439..9bebe5b 100644
+--- a/lib/operating_system.c
++++ b/lib/operating_system.c
+@@ -43,6 +43,7 @@ sr_operating_system_init(struct sr_operating_system *operating_system)
+     operating_system->version = NULL;
+     operating_system->architecture = NULL;
+     operating_system->cpe = NULL;
++    operating_system->variant = NULL;
+     operating_system->uptime = 0;
+ }
+ 
+@@ -56,6 +57,7 @@ sr_operating_system_free(struct sr_operating_system *operating_system)
+     free(operating_system->version);
+     free(operating_system->architecture);
+     free(operating_system->cpe);
++    free(operating_system->variant);
+     free(operating_system);
+ }
+ 
+@@ -92,6 +94,13 @@ sr_operating_system_to_json(struct sr_operating_system *operating_system)
+         sr_strbuf_append_str(strbuf, "\n");
+     }
+ 
++    if (operating_system->variant)
++    {
++        sr_strbuf_append_str(strbuf, ",   \"variant\": ");
++        sr_json_append_escaped(strbuf, operating_system->variant);
++        sr_strbuf_append_str(strbuf, "\n");
++    }
++
+     if (operating_system->uptime > 0)
+     {
+         sr_strbuf_append_strf(strbuf,
+@@ -122,6 +131,9 @@ sr_operating_system_from_json(struct sr_json_value *root, char **error_message)
+         JSON_READ_STRING(root, "architecture", &result->architecture) &&
+         JSON_READ_UINT64(root, "uptime", &result->uptime);
+ 
++    /* variant is optional - failure is not fatal */
++    JSON_READ_STRING(root, "variant", &result->variant);
++
+     if (!success)
+     {
+         sr_operating_system_free(result);
+@@ -194,6 +206,10 @@ os_release_callback(char *key, char *value, void *data)
+     {
+         operating_system->cpe = value;
+     }
++    else if (0 == strcmp(key, "VARIANT_ID"))
++    {
++        operating_system->variant = value;
++    }
+     else
+     {
+         free(value);
+diff --git a/tests/operating_system.at b/tests/operating_system.at
+index 0db2b9d..044d4bd 100644
+--- a/tests/operating_system.at
++++ b/tests/operating_system.at
+@@ -46,12 +46,14 @@ AT_TESTFUN([sr_operating_system_parse_etc_os_release],
+ [[
+ #include "operating_system.h"
+ #include "utils.h"
++#include <string.h>
+ #include <stdio.h>
+ #include <assert.h>
+ 
+ void check(const char *etc_os_release,
+            const char *expected_name,
+-           const char *expected_version)
++           const char *expected_version,
++           const char *expected_variant)
+ {
+     struct sr_operating_system *os = sr_operating_system_new();
+     bool success = sr_operating_system_parse_etc_os_release(
+@@ -60,6 +62,20 @@ void check(const char *etc_os_release,
+     assert(success);
+     assert(0 == strcmp(os->name, expected_name));
+     assert(0 == strcmp(os->version, expected_version));
++
++    if (   (    (expected_variant != NULL && os->variant == NULL)
++             || (expected_variant == NULL && os->variant != NULL)
++           )
++        || (    (expected_variant != NULL && os->variant != NULL)
++             &&  0 != strcmp(os->version, expected_version)
++            )
++        )
++    {
++        fprintf(stderr, "Expected: '%s'\n", expected_variant);
++        fprintf(stderr, "Got     : '%s'\n", os->variant);
++        abort();
++    }
++
+     sr_operating_system_free(os);
+ }
+ 
+@@ -102,9 +118,28 @@ main(void)
+ "REDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\n"
+ "REDHAT_SUPPORT_PRODUCT_VERSION=7.0\n";
+ 
+-    check(f19, "fedora", "19");
+-    check(raw, "fedora", "rawhide");
+-    check(el7, "rhel", "7.0");
++    char *f23 =
++"NAME=Fedora\n"
++"VERSION=\"23 (Workstation Edition)\"\n"
++"ID=fedora\n"
++"VERSION_ID=23\n"
++"PRETTY_NAME=\"Fedora 23 (Workstation Edition)\"\n"
++"ANSI_COLOR=\"0;34\"\n"
++"CPE_NAME=\"cpe:/o:fedoraproject:fedora:23\"\n"
++"HOME_URL=\"https://fedoraproject.org/\"\n"
++"BUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\n"
++"REDHAT_BUGZILLA_PRODUCT=\"Fedora\"\n"
++"REDHAT_BUGZILLA_PRODUCT_VERSION=23\n"
++"REDHAT_SUPPORT_PRODUCT=\"Fedora\"\n"
++"REDHAT_SUPPORT_PRODUCT_VERSION=23\n"
++"PRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy\n"
++"VARIANT=\"Workstation Edition\"\n"
++"VARIANT_ID=workstation\n";
++
++    check(f19, "fedora", "19", NULL);
++    check(raw, "fedora", "rawhide", NULL);
++    check(el7, "rhel", "7.0", NULL);
++    check(f23, "fedora", "23", "workstation");
+ 
+     return 0;
+ }
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-ppc64-backtrace-parsing.patch b/SOURCES/satyr-0.13-ppc64-backtrace-parsing.patch
new file mode 100644
index 0000000..fe5ac75
--- /dev/null
+++ b/SOURCES/satyr-0.13-ppc64-backtrace-parsing.patch
@@ -0,0 +1,153 @@
+From 0fcd63190deeb0afa2ad04826da99e5632f9d517 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 14 Jul 2014 16:53:12 +0200
+Subject: [SATYR PATCH 4/6] gdb: fix parsing of ppc64 stacktraces
+
+Fixes rhbz#1119072.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/gdb_frame.c                    |  3 +-
+ tests/gdb_stacktrace.at            | 35 ++++++++++++++++++++
+ tests/gdb_stacktraces/rhbz-1119072 | 67 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 104 insertions(+), 1 deletion(-)
+ create mode 100644 tests/gdb_stacktraces/rhbz-1119072
+
+diff --git a/lib/gdb_frame.c b/lib/gdb_frame.c
+index 57e9479..e449b30 100644
+--- a/lib/gdb_frame.c
++++ b/lib/gdb_frame.c
+@@ -559,12 +559,13 @@ sr_gdb_frame_parse_function_name(const char **input,
+     /* First character:
+        '~' for destructor
+        '*' for ????
++       '.' on ppc64
+        '_a-zA-Z' for mangled/nonmangled function name
+        '(' to start "(anonymous namespace)::" or something
+      */
+     char first;
+     char *namechunk;
+-    if (sr_parse_char_limited(&local_input, "~*_" SR_alpha, &first))
++    if (sr_parse_char_limited(&local_input, "~*._" SR_alnum, &first))
+     {
+         /* If it's a start of 'o'perator, put the 'o' back! */
+         if (first == 'o')
+diff --git a/tests/gdb_stacktrace.at b/tests/gdb_stacktrace.at
+index 8832cb8..f626417 100644
+--- a/tests/gdb_stacktrace.at
++++ b/tests/gdb_stacktrace.at
+@@ -247,3 +247,38 @@ main(void)
+   return 0;
+ }
+ ]])
++
++## ----------------------------- ##
++## sr_gdb_stacktrace_parse_ppc64 ##
++## ----------------------------- ##
++AT_TESTFUN([sr_gdb_stacktrace_parse_ppc64],
++[[
++#include "gdb/stacktrace.h"
++#include "gdb/thread.h"
++#include "gdb/frame.h"
++#include "location.h"
++#include "utils.h"
++#include <assert.h>
++#include <stdio.h>
++
++int
++main(void)
++{
++  /* Check that satyr is able to parse backtrace with missing Thread header */
++  struct sr_location location;
++  sr_location_init(&location);
++  char *error_message;
++  char *full_input = sr_file_to_string("../../gdb_stacktraces/rhbz-1119072", &error_message);
++  assert(full_input);
++  char *input = full_input;
++  struct sr_gdb_stacktrace *stacktrace = sr_gdb_stacktrace_parse(&input, &location);
++  assert(stacktrace);
++  assert(5 == sr_gdb_stacktrace_get_thread_count(stacktrace));
++  struct sr_gdb_thread *thread = stacktrace->threads;
++  assert(7 == sr_thread_frame_count(thread));
++  struct sr_gdb_frame *frame = thread->frames;
++  assert(0 == strcmp(frame->function_name, ".pthread_cond_timedwait"));
++  sr_gdb_stacktrace_free(stacktrace);
++  return 0;
++}
++]])
+diff --git a/tests/gdb_stacktraces/rhbz-1119072 b/tests/gdb_stacktraces/rhbz-1119072
+new file mode 100644
+index 0000000..2aca936
+--- /dev/null
++++ b/tests/gdb_stacktraces/rhbz-1119072
+@@ -0,0 +1,67 @@
++[New LWP 2271]
++[New LWP 2374]
++[New LWP 2325]
++[New LWP 2360]
++[New LWP 2375]
++[Thread debugging using libthread_db enabled]
++Using host libthread_db library "/lib64/libthread_db.so.1".
++Core was generated by `caja '.
++Program terminated with signal SIGTRAP, Trace/breakpoint trap.
++#0  0x000000803a327a54 in .raise () from /usr/lib64/libpthread.so.0
++
++Thread 5 (Thread 0x3fffa0ebeed0 (LWP 2375)):
++#0  0x000000803a321f24 in .pthread_cond_timedwait () from /usr/lib64/libpthread.so.0
++#1  0x000000803a748054 in .g_cond_wait_until () from /usr/lib64/libglib-2.0.so.0
++#2  0x000000803a6ac2c8 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#3  0x000000803a720530 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#4  0x000000803a71f3f8 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#5  0x000000803a31c31c in .start_thread () from /usr/lib64/libpthread.so.0
++#6  0x000000803a20c150 in .__clone () from /usr/lib64/libc.so.6
++
++Thread 4 (Thread 0x3fffa208eed0 (LWP 2360)):
++#0  0x000000803a1fd130 in .__GI___poll () from /usr/lib64/libc.so.6
++#1  0x000000803a6fe0b0 in .g_poll () from /usr/lib64/libglib-2.0.so.0
++#2  0x000000803a6e9ef4 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#3  0x000000803a6ea610 in .g_main_loop_run () from /usr/lib64/libglib-2.0.so.0
++#4  0x000000803ae658c8 in 0000090f.plt_call.g_io_channel_flush+0 () from /usr/lib64/libgio-2.0.so.0
++#5  0x000000803a71f3f8 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#6  0x000000803a31c31c in .start_thread () from /usr/lib64/libpthread.so.0
++#7  0x000000803a20c150 in .__clone () from /usr/lib64/libc.so.6
++
++Thread 3 (Thread 0x3fffa288eed0 (LWP 2325)):
++#0  0x000000803a1fd130 in .__GI___poll () from /usr/lib64/libc.so.6
++#1  0x000000803a6fe0b0 in .g_poll () from /usr/lib64/libglib-2.0.so.0
++#2  0x000000803a6e9ef4 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#3  0x000000803a6ea0a4 in .g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0
++#4  0x00003fffa28ba8dc in 00000010.plt_call.g_hash_table_size+0 () from /usr/lib64/gio/modules/libdconfsettings.so
++#5  0x000000803a71f3f8 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#6  0x000000803a31c31c in .start_thread () from /usr/lib64/libpthread.so.0
++#7  0x000000803a20c150 in .__clone () from /usr/lib64/libc.so.6
++
++Thread 2 (Thread 0x3fffa16beed0 (LWP 2374)):
++#0  0x000000803a1fd130 in .__GI___poll () from /usr/lib64/libc.so.6
++#1  0x000000803a6fe0b0 in .g_poll () from /usr/lib64/libglib-2.0.so.0
++#2  0x000000803a6e9ef4 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#3  0x000000803a6ea0a4 in .g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0
++#4  0x000000803a6ea15c in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#5  0x000000803a71f3f8 in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#6  0x000000803a31c31c in .start_thread () from /usr/lib64/libpthread.so.0
++#7  0x000000803a20c150 in .__clone () from /usr/lib64/libc.so.6
++
++Thread 1 (Thread 0x3fffa9210490 (LWP 2271)):
++#0  0x000000803a327a54 in .raise () from /usr/lib64/libpthread.so.0
++#1  0x000000803a6f3dd8 in .g_logv () from /usr/lib64/libglib-2.0.so.0
++#2  0x000000803a6f3ef8 in .g_log () from /usr/lib64/libglib-2.0.so.0
++#3  0x000000803cd0c230 in 000002cd.plt_call.XmbTextExtents+0 () from /usr/lib64/libgdk-x11-2.0.so.0
++#4  0x000000803a9e0870 in ._XError () from /usr/lib64/libX11.so.6
++#5  0x000000803a9dc398 in 00000e9e.plt_call._XimXTransGetHostname+0 () from /usr/lib64/libX11.so.6
++#6  0x000000803a9dc514 in 00000e9e.plt_call._XimXTransGetHostname+0 () from /usr/lib64/libX11.so.6
++#7  0x000000803a9dd19c in ._XEventsQueued () from /usr/lib64/libX11.so.6
++#8  0x000000803a9c8d6c in .XPending () from /usr/lib64/libX11.so.6
++#9  0x000000803ccfbd74 in 000002cd.plt_call.XmbTextExtents+0 () from /usr/lib64/libgdk-x11-2.0.so.0
++#10 0x000000803ccfbf58 in 000002cd.plt_call.XmbTextExtents+0 () from /usr/lib64/libgdk-x11-2.0.so.0
++#11 0x000000803a6e9124 in .g_main_context_prepare () from /usr/lib64/libglib-2.0.so.0
++#12 0x000000803a6e9d7c in 000000ca.plt_call.strncasecmp@@GLIBC_2.3+0 () from /usr/lib64/libglib-2.0.so.0
++#13 0x000000803a6ea610 in .g_main_loop_run () from /usr/lib64/libglib-2.0.so.0
++#14 0x000000803c839050 in .gtk_main () from /usr/lib64/libgtk-x11-2.0.so.0
++#15 0x0000000010051054 in .main ()
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-python-add-Ruby-support.patch b/SOURCES/satyr-0.13-python-add-Ruby-support.patch
new file mode 100644
index 0000000..cb2c8a0
--- /dev/null
+++ b/SOURCES/satyr-0.13-python-add-Ruby-support.patch
@@ -0,0 +1,914 @@
+From b0c2427cdc59be0bb1fa76e928b17d9aa1a0d4eb Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Wed, 28 Jan 2015 17:31:41 +0100
+Subject: [PATCH] python: add Ruby support
+
+Related: #1334604
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+
+Conflicts:
+	tests/Makefile.am
+---
+ python/Makefile.am          |   4 +
+ python/py_module.c          |  22 +++++
+ python/py_report.c          |   9 ++
+ python/py_ruby_frame.c      | 227 ++++++++++++++++++++++++++++++++++++++++++++
+ python/py_ruby_frame.h      |  69 ++++++++++++++
+ python/py_ruby_stacktrace.c | 182 +++++++++++++++++++++++++++++++++++
+ python/py_ruby_stacktrace.h |  71 ++++++++++++++
+ tests/Makefile.am           |  11 ++-
+ tests/python/ruby.py        | 174 +++++++++++++++++++++++++++++++++
+ tests/python_bindings.at    |   1 +
+ 10 files changed, 769 insertions(+), 1 deletion(-)
+ create mode 100644 python/py_ruby_frame.c
+ create mode 100644 python/py_ruby_frame.h
+ create mode 100644 python/py_ruby_stacktrace.c
+ create mode 100644 python/py_ruby_stacktrace.h
+ create mode 100755 tests/python/ruby.py
+
+diff --git a/python/Makefile.am b/python/Makefile.am
+index 83273a2..9380c12 100644
+--- a/python/Makefile.am
++++ b/python/Makefile.am
+@@ -44,6 +44,10 @@ _satyr_la_SOURCES = \
+     py_java_thread.c \
+     py_java_stacktrace.h \
+     py_java_stacktrace.c \
++    py_ruby_frame.h \
++    py_ruby_frame.c \
++    py_ruby_stacktrace.h \
++    py_ruby_stacktrace.c \
+     py_rpm_package.h \
+     py_rpm_package.c \
+     py_metrics.h \
+diff --git a/python/py_module.c b/python/py_module.c
+index 83697ef..144465b 100644
+--- a/python/py_module.c
++++ b/python/py_module.c
+@@ -17,6 +17,8 @@
+ #include "py_core_frame.h"
+ #include "py_core_thread.h"
+ #include "py_core_stacktrace.h"
++#include "py_ruby_frame.h"
++#include "py_ruby_stacktrace.h"
+ #include "py_rpm_package.h"
+ #include "py_metrics.h"
+ #include "py_operating_system.h"
+@@ -187,6 +189,18 @@ init_satyr()
+         return;
+     }
+ 
++    if (PyType_Ready(&sr_py_ruby_frame_type) < 0)
++    {
++        puts("PyType_Ready(&sr_py_ruby_frame_type) < 0");
++        return;
++    }
++
++    if (PyType_Ready(&sr_py_ruby_stacktrace_type) < 0)
++    {
++        puts("PyType_Ready(&sr_py_ruby_stacktrace_type) < 0");
++        return;
++    }
++
+     if (PyType_Ready(&sr_py_operating_system_type) < 0)
+     {
+         puts("PyType_Ready(&sr_py_operating_system_type) < 0");
+@@ -301,6 +315,14 @@ init_satyr()
+     PyModule_AddObject(module, "JavaStacktrace",
+                        (PyObject *)&sr_py_java_stacktrace_type);
+ 
++    Py_INCREF(&sr_py_ruby_frame_type);
++    PyModule_AddObject(module, "RubyFrame",
++                       (PyObject *)&sr_py_ruby_frame_type);
++
++    Py_INCREF(&sr_py_ruby_stacktrace_type);
++    PyModule_AddObject(module, "RubyStacktrace",
++                       (PyObject *)&sr_py_ruby_stacktrace_type);
++
+     Py_INCREF(&sr_py_core_frame_type);
+     PyModule_AddObject(module, "CoreFrame",
+                        (PyObject *)&sr_py_core_frame_type);
+diff --git a/python/py_report.c b/python/py_report.c
+index 8aa35dd..74cd9dd 100644
+--- a/python/py_report.c
++++ b/python/py_report.c
+@@ -28,6 +28,7 @@
+ #include "py_python_stacktrace.h"
+ #include "py_koops_stacktrace.h"
+ #include "py_java_stacktrace.h"
++#include "py_ruby_stacktrace.h"
+ 
+ #include "report.h"
+ #include "operating_system.h"
+@@ -284,6 +285,10 @@ report_prepare_subobjects(struct sr_py_report *report)
+             if (stacktrace_prepare(report, &sr_py_koops_stacktrace_type, false) < 0)
+                 return -1;
+             break;
++        case SR_REPORT_RUBY:
++            if (stacktrace_prepare(report, &sr_py_ruby_stacktrace_type, false) < 0)
++                return -1;
++            break;
+         default:
+             report->report->stacktrace = NULL;
+             break;
+@@ -347,6 +352,10 @@ report_to_python_obj(struct sr_report *report)
+             ro->stacktrace = koops_stacktrace_to_python_obj(
+                     (struct sr_koops_stacktrace *)report->stacktrace);
+             break;
++        case SR_REPORT_RUBY:
++            ro->stacktrace = ruby_stacktrace_to_python_obj(
++                    (struct sr_ruby_stacktrace *)report->stacktrace);
++            break;
+         default:
+             Py_INCREF(Py_None);
+             ro->stacktrace = Py_None;
+diff --git a/python/py_ruby_frame.c b/python/py_ruby_frame.c
+new file mode 100644
+index 0000000..9ed45ad
+--- /dev/null
++++ b/python/py_ruby_frame.c
+@@ -0,0 +1,227 @@
++/*
++    py_ruby_frame.c
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#include "py_common.h"
++#include "py_base_frame.h"
++#include "py_ruby_frame.h"
++#include "location.h"
++#include "strbuf.h"
++#include "utils.h"
++#include "ruby/frame.h"
++
++#define frame_doc "satyr.RubyFrame - class representing a ruby frame\n\n" \
++                  "Usage:\n\n" \
++                  "satyr.RubyFrame() - creates an empty frame\n\n" \
++                  "satyr.RubyFrame(str) - parses str and fills the frame object"
++
++#define f_dup_doc "Usage: frame.dup()\n\n" \
++                  "Returns: satyr.RubyFrame - a new clone of frame\n\n" \
++                  "Clones the frame object. All new structures are independent " \
++                  "of the original object."
++
++
++static PyMethodDef
++frame_methods[] =
++{
++    /* methods */
++    { "dup", sr_py_ruby_frame_dup, METH_NOARGS,  f_dup_doc },
++    { NULL },
++};
++
++/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
++#define GSOFF_PY_STRUCT sr_py_ruby_frame
++#define GSOFF_PY_MEMBER frame
++#define GSOFF_C_STRUCT sr_ruby_frame
++GSOFF_START
++GSOFF_MEMBER(file_name),
++GSOFF_MEMBER(file_line),
++GSOFF_MEMBER(special_function),
++GSOFF_MEMBER(function_name),
++GSOFF_MEMBER(block_level),
++GSOFF_MEMBER(rescue_level)
++GSOFF_END
++
++static PyGetSetDef
++frame_getset[] =
++{
++    SR_ATTRIBUTE_STRING(file_name,        "Source file name (string)"                              ),
++    SR_ATTRIBUTE_UINT32(file_line,        "Source line number (positive integer)"                  ),
++    SR_ATTRIBUTE_BOOL  (special_function, "True if the frame doesn't belong to an actual function" ),
++    SR_ATTRIBUTE_STRING(function_name,    "Function name (string)"                                 ),
++    SR_ATTRIBUTE_UINT32(block_level,      "Block nesting level (integer)"                          ),
++    SR_ATTRIBUTE_UINT32(rescue_level,     "Rescue block nesting level (integer)"                   ),
++    { NULL },
++};
++
++PyTypeObject
++sr_py_ruby_frame_type =
++{
++    PyObject_HEAD_INIT(NULL)
++    0,
++    "satyr.RubyFrame",          /* tp_name */
++    sizeof(struct sr_py_ruby_frame), /* tp_basicsize */
++    0,                          /* tp_itemsize */
++    sr_py_ruby_frame_free,      /* tp_dealloc */
++    NULL,                       /* tp_print */
++    NULL,                       /* tp_getattr */
++    NULL,                       /* tp_setattr */
++    NULL,                       /* tp_compare */
++    NULL,                       /* tp_repr */
++    NULL,                       /* tp_as_number */
++    NULL,                       /* tp_as_sequence */
++    NULL,                       /* tp_as_mapping */
++    NULL,                       /* tp_hash */
++    NULL,                       /* tp_call */
++    sr_py_ruby_frame_str,       /* tp_str */
++    NULL,                       /* tp_getattro */
++    NULL,                       /* tp_setattro */
++    NULL,                       /* tp_as_buffer */
++    Py_TPFLAGS_DEFAULT,         /* tp_flags */
++    frame_doc,                  /* tp_doc */
++    NULL,                       /* tp_traverse */
++    NULL,                       /* tp_clear */
++    NULL,                       /* tp_richcompare */
++    0,                          /* tp_weaklistoffset */
++    NULL,                       /* tp_iter */
++    NULL,                       /* tp_iternext */
++    frame_methods,              /* tp_methods */
++    NULL,                       /* tp_members */
++    frame_getset,               /* tp_getset */
++    &sr_py_base_frame_type,     /* tp_base */
++    NULL,                       /* tp_dict */
++    NULL,                       /* tp_descr_get */
++    NULL,                       /* tp_descr_set */
++    0,                          /* tp_dictoffset */
++    NULL,                       /* tp_init */
++    NULL,                       /* tp_alloc */
++    sr_py_ruby_frame_new,       /* tp_new */
++    NULL,                       /* tp_free */
++    NULL,                       /* tp_is_gc */
++    NULL,                       /* tp_bases */
++    NULL,                       /* tp_mro */
++    NULL,                       /* tp_cache */
++    NULL,                       /* tp_subclasses */
++    NULL,                       /* tp_weaklist */
++};
++
++/* constructor */
++PyObject *
++sr_py_ruby_frame_new(PyTypeObject *object, PyObject *args, PyObject *kwds)
++{
++    struct sr_py_ruby_frame *fo = (struct sr_py_ruby_frame*)
++        PyObject_New(struct sr_py_ruby_frame, &sr_py_ruby_frame_type);
++
++    if (!fo)
++        return PyErr_NoMemory();
++
++    const char *str = NULL;
++    if (!PyArg_ParseTuple(args, "|s", &str))
++        return NULL;
++
++    if (str)
++    {
++        struct sr_location location;
++        sr_location_init(&location);
++        fo->frame = sr_ruby_frame_parse(&str, &location);
++
++        if (!fo->frame)
++        {
++            PyErr_SetString(PyExc_ValueError, location.message);
++            return NULL;
++        }
++    }
++    else
++        fo->frame = sr_ruby_frame_new();
++
++    return (PyObject*)fo;
++}
++
++/* destructor */
++void
++sr_py_ruby_frame_free(PyObject *object)
++{
++    struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)object;
++    sr_ruby_frame_free(this->frame);
++    PyObject_Del(object);
++}
++
++/* str */
++PyObject *
++sr_py_ruby_frame_str(PyObject *self)
++{
++    struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)self;
++    struct sr_strbuf *buf = sr_strbuf_new();
++
++
++    if (this->frame->file_name)
++    {
++        sr_strbuf_append_str(buf, this->frame->file_name);
++    }
++
++    if (this->frame->file_line)
++    {
++        sr_strbuf_append_strf(buf, ":%d", this->frame->file_line);
++    }
++
++    if (this->frame->function_name)
++    {
++        sr_strbuf_append_str(buf, ":in `");
++
++        int i;
++        for (i = 0; i < this->frame->rescue_level; i++)
++        {
++            sr_strbuf_append_str(buf, "rescue in ");
++        }
++
++        if (this->frame->block_level == 1)
++        {
++            sr_strbuf_append_str(buf, "block in ");
++        }
++        else if (this->frame->block_level > 1)
++        {
++            sr_strbuf_append_strf(buf, "block (%d levels) in ", this->frame->block_level);
++        }
++
++        sr_strbuf_append_strf(buf, "%s%s%s'",
++                              (this->frame->special_function ? "<" : ""),
++                              this->frame->function_name,
++                              (this->frame->special_function ? ">" : ""));
++    }
++
++    char *str = sr_strbuf_free_nobuf(buf);
++    PyObject *result = Py_BuildValue("s", str);
++    free(str);
++    return result;
++}
++
++/* methods */
++PyObject *
++sr_py_ruby_frame_dup(PyObject *self, PyObject *args)
++{
++    struct sr_py_ruby_frame *this = (struct sr_py_ruby_frame*)self;
++    struct sr_py_ruby_frame *fo = (struct sr_py_ruby_frame*)
++        PyObject_New(struct sr_py_ruby_frame, &sr_py_ruby_frame_type);
++
++    if (!fo)
++        return PyErr_NoMemory();
++
++    fo->frame = sr_ruby_frame_dup(this->frame, false);
++
++    return (PyObject*)fo;
++}
+diff --git a/python/py_ruby_frame.h b/python/py_ruby_frame.h
+new file mode 100644
+index 0000000..7f460ff
+--- /dev/null
++++ b/python/py_ruby_frame.h
+@@ -0,0 +1,69 @@
++/*
++    py_ruby_frame.h
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_PY_RUBY_FRAME_H
++#define SATYR_PY_RUBY_FRAME_H
++
++/**
++ * @file
++ * @brief Python bindings for RUBY frame.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include <Python.h>
++#include <structmember.h>
++
++PyTypeObject sr_py_ruby_frame_type;
++
++/* The beginning of this structure has to have the same layout as
++ * sr_py_base_frame.
++ */
++struct sr_py_ruby_frame
++{
++    PyObject_HEAD
++    struct sr_ruby_frame *frame;
++};
++
++/**
++ * Constructor.
++ */
++PyObject *sr_py_ruby_frame_new(PyTypeObject *object,
++                               PyObject *args, PyObject *kwds);
++
++/**
++ * Destructor.
++ */
++void sr_py_ruby_frame_free(PyObject *object);
++
++/**
++ * str
++ */
++PyObject *sr_py_ruby_frame_str(PyObject *self);
++
++/* methods */
++PyObject *sr_py_ruby_frame_dup(PyObject *self, PyObject *args);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/python/py_ruby_stacktrace.c b/python/py_ruby_stacktrace.c
+new file mode 100644
+index 0000000..97fbb44
+--- /dev/null
++++ b/python/py_ruby_stacktrace.c
+@@ -0,0 +1,182 @@
++#include "py_common.h"
++#include "py_ruby_frame.h"
++#include "py_ruby_stacktrace.h"
++#include "py_base_stacktrace.h"
++#include "utils.h"
++#include "strbuf.h"
++#include "ruby/frame.h"
++#include "ruby/stacktrace.h"
++#include "location.h"
++#include "stacktrace.h"
++
++#define stacktrace_doc "satyr.RubyStacktrace - class representing a ruby stacktrace\n\n" \
++                       "Usage:\n\n" \
++                       "satyr.RubyStacktrace() - creates an empty ruby stacktrace\n\n" \
++                       "satyr.RubyStacktrace(str) - parses str and fills the ruby stacktrace object"
++
++#define f_dup_doc "Usage: stacktrace.dup()\n\n" \
++                  "Returns: satyr.RubyStacktrace - a new clone of ruby stacktrace\n\n" \
++                  "Clones the RubyStacktrace object. All new structures are independent " \
++                  "of the original object."
++
++
++static PyMethodDef
++ruby_stacktrace_methods[] =
++{
++    /* methods */
++    { "dup", sr_py_ruby_stacktrace_dup, METH_NOARGS, f_dup_doc },
++    { NULL },
++};
++
++/* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
++#define GSOFF_PY_STRUCT sr_py_ruby_stacktrace
++#define GSOFF_PY_MEMBER stacktrace
++#define GSOFF_C_STRUCT sr_ruby_stacktrace
++GSOFF_START
++GSOFF_MEMBER(exception_name)
++GSOFF_END
++
++static PyGetSetDef
++ruby_stacktrace_getset[] =
++{
++    SR_ATTRIBUTE_STRING(exception_name, "Exception type (string)"              ),
++    { NULL },
++};
++
++PyTypeObject sr_py_ruby_stacktrace_type = {
++    PyObject_HEAD_INIT(NULL)
++    0,
++    "satyr.RubyStacktrace",         /* tp_name */
++    sizeof(struct sr_py_ruby_stacktrace), /* tp_basicsize */
++    0,                              /* tp_itemsize */
++    sr_py_ruby_stacktrace_free,     /* tp_dealloc */
++    NULL,                           /* tp_print */
++    NULL,                           /* tp_getattr */
++    NULL,                           /* tp_setattr */
++    NULL,                           /* tp_compare */
++    NULL,                           /* tp_repr */
++    NULL,                           /* tp_as_number */
++    NULL,                           /* tp_as_sequence */
++    NULL,                           /* tp_as_mapping */
++    NULL,                           /* tp_hash */
++    NULL,                           /* tp_call */
++    sr_py_ruby_stacktrace_str,      /* tp_str */
++    NULL,                           /* tp_getattro */
++    NULL,                           /* tp_setattro */
++    NULL,                           /* tp_as_buffer */
++    Py_TPFLAGS_DEFAULT,             /* tp_flags */
++    stacktrace_doc,                 /* tp_doc */
++    NULL,                           /* tp_traverse */
++    NULL,                           /* tp_clear */
++    NULL,                           /* tp_richcompare */
++    0,                              /* tp_weaklistoffset */
++    NULL,                           /* tp_iter */
++    NULL,                           /* tp_iternext */
++    ruby_stacktrace_methods,        /* tp_methods */
++    NULL,                           /* tp_members */
++    ruby_stacktrace_getset,         /* tp_getset */
++    &sr_py_single_stacktrace_type,  /* tp_base */
++    NULL,                           /* tp_dict */
++    NULL,                           /* tp_descr_get */
++    NULL,                           /* tp_descr_set */
++    0,                              /* tp_dictoffset */
++    NULL,                           /* tp_init */
++    NULL,                           /* tp_alloc */
++    sr_py_ruby_stacktrace_new,      /* tp_new */
++    NULL,                           /* tp_free */
++    NULL,                           /* tp_is_gc */
++    NULL,                           /* tp_bases */
++    NULL,                           /* tp_mro */
++    NULL,                           /* tp_cache */
++    NULL,                           /* tp_subclasses */
++    NULL,                           /* tp_weaklist */
++};
++
++PyObject *
++ruby_stacktrace_to_python_obj(struct sr_ruby_stacktrace *stacktrace)
++{
++    struct sr_py_ruby_stacktrace *bo = PyObject_New(struct sr_py_ruby_stacktrace,
++                                                    &sr_py_ruby_stacktrace_type);
++    if (!bo)
++        return PyErr_NoMemory();
++
++    bo->frame_type = &sr_py_ruby_frame_type;
++
++    bo->stacktrace = stacktrace;
++    bo->frames = frames_to_python_list((struct sr_thread *)bo->stacktrace,
++                                       bo->frame_type);
++    if (!bo->frames)
++        return NULL;
++
++    return (PyObject *)bo;
++}
++
++/* constructor */
++PyObject *
++sr_py_ruby_stacktrace_new(PyTypeObject *object,
++                            PyObject *args,
++                            PyObject *kwds)
++{
++    const char *str = NULL;
++    if (!PyArg_ParseTuple(args, "|s", &str))
++        return NULL;
++
++    struct sr_ruby_stacktrace *stacktrace;
++
++    if (str)
++    {
++        struct sr_location location;
++        sr_location_init(&location);
++        stacktrace = sr_ruby_stacktrace_parse(&str, &location);
++        if (!stacktrace)
++        {
++            PyErr_SetString(PyExc_ValueError, location.message);
++            return NULL;
++        }
++    }
++    else
++        stacktrace = sr_ruby_stacktrace_new();
++
++    return ruby_stacktrace_to_python_obj(stacktrace);
++}
++
++/* destructor */
++void
++sr_py_ruby_stacktrace_free(PyObject *object)
++{
++    struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace*)object;
++    /* the list will decref all of its elements */
++    Py_DECREF(this->frames);
++    this->stacktrace->frames = NULL;
++    sr_ruby_stacktrace_free(this->stacktrace);
++    PyObject_Del(object);
++}
++
++/* str */
++PyObject *
++sr_py_ruby_stacktrace_str(PyObject *self)
++{
++    struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace *)self;
++    struct sr_strbuf *buf = sr_strbuf_new();
++    sr_strbuf_append_strf(buf, "Ruby stacktrace with %zd frames",
++                         (ssize_t)(PyList_Size(this->frames)));
++    char *str = sr_strbuf_free_nobuf(buf);
++    PyObject *result = Py_BuildValue("s", str);
++    free(str);
++    return result;
++}
++
++/* methods */
++PyObject *
++sr_py_ruby_stacktrace_dup(PyObject *self, PyObject *args)
++{
++    struct sr_py_ruby_stacktrace *this = (struct sr_py_ruby_stacktrace*)self;
++    if (frames_prepare_linked_list((struct sr_py_base_thread *)this) < 0)
++        return NULL;
++
++    struct sr_ruby_stacktrace *stacktrace = sr_ruby_stacktrace_dup(this->stacktrace);
++    if (!stacktrace)
++        return NULL;
++
++    return ruby_stacktrace_to_python_obj(stacktrace);
++}
+diff --git a/python/py_ruby_stacktrace.h b/python/py_ruby_stacktrace.h
+new file mode 100644
+index 0000000..f4eebce
+--- /dev/null
++++ b/python/py_ruby_stacktrace.h
+@@ -0,0 +1,71 @@
++/*
++    py_ruby_stacktrace.h
++
++    Copyright (C) 2015  Red Hat, Inc.
++
++    This program is free software; you can redistribute it and/or modify
++    it under the terms of the GNU General Public License as published by
++    the Free Software Foundation; either version 2 of the License, or
++    (at your option) any later version.
++
++    This program is distributed in the hope that it will be useful,
++    but WITHOUT ANY WARRANTY; without even the implied warranty of
++    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++    GNU General Public License for more details.
++
++    You should have received a copy of the GNU General Public License along
++    with this program; if not, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef SATYR_PY_RUBY_STACKTRACE_H
++#define SATYR_PY_RUBY_STACKTRACE_H
++
++/**
++ * @file
++ * @brief Python bindings for Ruby stack trace.
++ */
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include <Python.h>
++#include <structmember.h>
++
++PyTypeObject sr_py_ruby_stacktrace_type;
++
++/* The beginning of this structure has to have the same layout as
++ * sr_py_base_thread.
++ */
++struct sr_py_ruby_stacktrace
++{
++    PyObject_HEAD
++    struct sr_ruby_stacktrace *stacktrace;
++    PyObject *frames;
++    PyTypeObject *frame_type;
++};
++
++/* helpers */
++PyObject *ruby_stacktrace_to_python_obj(struct sr_ruby_stacktrace *stacktrace);
++
++/* constructor */
++PyObject *sr_py_ruby_stacktrace_new(PyTypeObject *object,
++                                    PyObject *args,
++                                    PyObject *kwds);
++
++/* destructor */
++void sr_py_ruby_stacktrace_free(PyObject *object);
++
++/* str */
++PyObject *sr_py_ruby_stacktrace_str(PyObject *self);
++
++/* methods */
++PyObject *sr_py_ruby_stacktrace_dup(PyObject *self, PyObject *args);
++PyObject *sr_py_ruby_stacktrace_normalize(PyObject *self, PyObject *args);
++
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index 0eb33a5..b261880 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -4,7 +4,16 @@ EXTRA_DIST = gdb_stacktraces \
+              json_files \
+              kerneloopses \
+              programs \
+-             python \
++             python/core.py \
++             python/gdb.py \
++             python/java.py \
++             python/koops.py \
++             python/metrics.py \
++             python/misc.py \
++             python/python.py \
++             python/report.py \
++             python/ruby.py \
++             python/test_helpers.py \
+              python_stacktraces
+ 
+ ## ------------ ##
+diff --git a/tests/python/ruby.py b/tests/python/ruby.py
+new file mode 100755
+index 0000000..8a20593
+--- /dev/null
++++ b/tests/python/ruby.py
+@@ -0,0 +1,174 @@
++#!/usr/bin/env python
++
++import unittest
++from test_helpers import *
++
++path = '../ruby_stacktraces/ruby-01'
++contents = load_input_contents(path)
++frames_expected = 21
++expected_short_text = '''#1 rescue in block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:13
++#2 block (2 levels) in func in /usr/share/ruby/vendor_ruby/will_crash.rb:10
++#3 times in /usr/share/ruby/vendor_ruby/will_crash.rb:9
++#4 block in func in /usr/share/ruby/vendor_ruby/will_crash.rb:9
++#5 times in /usr/share/ruby/vendor_ruby/will_crash.rb:8
++#6 func in /usr/share/ruby/vendor_ruby/will_crash.rb:8
++'''
++
++class TestRubyStacktrace(BindingsTestCase):
++    def setUp(self):
++        self.trace = satyr.RubyStacktrace(contents)
++
++    def test_correct_frame_count(self):
++        self.assertEqual(frame_count(self.trace), frames_expected)
++
++    def test_dup(self):
++        dup = self.trace.dup()
++        self.assertNotEqual(id(dup.frames), id(self.trace.frames))
++        self.assertTrue(all(map(lambda t1, t2: t1.equals(t2), dup.frames, self.trace.frames)))
++
++        dup.frames = dup.frames[:5]
++        dup2 = dup.dup()
++        self.assertEqual(len(dup.frames), len(dup2.frames))
++        self.assertNotEqual(id(dup.frames), id(dup2.frames))
++
++    def test_prepare_linked_list(self):
++        dup = self.trace.dup()
++        dup.frames = dup.frames[:5]
++        dup2 = dup.dup()
++        self.assertTrue(len(dup2.frames) <= 5)
++
++    def test_str(self):
++        out = str(self.trace)
++        self.assertTrue(('Ruby stacktrace with %d frames' % frames_expected) in out)
++
++    def test_getset(self):
++        self.assertGetSetCorrect(self.trace, 'exception_name', 'Wrap::MyException', 'WhateverException')
++
++    def test_special_functions(self):
++        trace = load_input_contents('../ruby_stacktraces/ruby-01')
++        trace = satyr.RubyStacktrace(trace)
++
++        f = trace.frames[0]
++        self.assertEqual(f.file_name, '/usr/share/ruby/vendor_ruby/will_crash.rb')
++        self.assertEqual(f.function_name, 'func')
++        self.assertFalse(f.special_function)
++        self.assertEqual(f.block_level, 2)
++        self.assertEqual(f.rescue_level, 1)
++
++        f = trace.frames[-1]
++        self.assertEqual(f.file_name, '/usr/bin/will_ruby_raise')
++        self.assertEqual(f.function_name, 'main')
++        self.assertTrue(f.special_function)
++        self.assertEqual(f.block_level, 0)
++        self.assertEqual(f.rescue_level, 0)
++
++    def test_to_short_text(self):
++        self.assertEqual(self.trace.to_short_text(6), expected_short_text)
++
++    def test_bthash(self):
++        self.assertEqual(self.trace.get_bthash(), '6124e03542a0381b14ebaf2c5b5e9f467ebba33b')
++
++    def test_duphash(self):
++        expected_plain = '''Thread
++/usr/share/ruby/vendor_ruby/will_crash.rb:13
++/usr/share/ruby/vendor_ruby/will_crash.rb:10
++/usr/share/ruby/vendor_ruby/will_crash.rb:9
++'''
++        self.assertEqual(self.trace.get_duphash(flags=satyr.DUPHASH_NOHASH, frames=3), expected_plain)
++        self.assertEqual(self.trace.get_duphash(), 'c877cda04fbce8d51c4d9b1d628b0f618677607e')
++
++    def test_crash_thread(self):
++        self.assertTrue(self.trace.crash_thread is self.trace)
++
++    def test_from_json(self):
++        trace = satyr.RubyStacktrace.from_json('{}')
++        self.assertEqual(trace.frames, [])
++
++        json_text = '''
++        {
++            "exception_name": "NotImplementedError",
++            "stacktrace": [
++                {
++                    "file_name": "/usr/share/foobar/mod/file.rb",
++                    "function_name": "do_nothing",
++                    "file_line": 42,
++                },
++                {
++                    "special_function": "top (required)",
++                    "file_line": 451
++                },
++                {
++                    "unknown_key": 19876543,
++                    "block_level": 42,
++                    "rescue_level": 6
++                }
++            ]
++        }
++'''
++        trace = satyr.RubyStacktrace.from_json(json_text)
++        self.assertEqual(trace.exception_name, 'NotImplementedError')
++        self.assertEqual(len(trace.frames), 3)
++
++        self.assertEqual(trace.frames[0].file_name, '/usr/share/foobar/mod/file.rb')
++        self.assertEqual(trace.frames[0].function_name, 'do_nothing')
++        self.assertEqual(trace.frames[0].file_line, 42)
++        self.assertFalse(trace.frames[0].special_function)
++        self.assertEqual(trace.frames[0].block_level, 0)
++        self.assertEqual(trace.frames[0].rescue_level, 0)
++
++        self.assertEqual(trace.frames[1].file_name, None)
++        self.assertEqual(trace.frames[1].function_name, 'top (required)')
++        self.assertEqual(trace.frames[1].file_line, 451)
++        self.assertTrue(trace.frames[1].special_function)
++        self.assertEqual(trace.frames[1].block_level, 0)
++        self.assertEqual(trace.frames[1].rescue_level, 0)
++
++        self.assertEqual(trace.frames[2].file_name, None)
++        self.assertEqual(trace.frames[2].function_name, None)
++        self.assertEqual(trace.frames[2].file_line, 0)
++        self.assertFalse(trace.frames[2].special_function)
++        self.assertEqual(trace.frames[2].block_level, 42)
++        self.assertEqual(trace.frames[2].rescue_level, 6)
++
++    def test_hash(self):
++        self.assertHashable(self.trace)
++
++
++class TestRubyFrame(BindingsTestCase):
++    def setUp(self):
++        self.frame = satyr.RubyStacktrace(contents).frames[1]
++
++    def test_str(self):
++        out = str(self.frame)
++        self.assertEqual(out, "/usr/share/ruby/vendor_ruby/will_crash.rb:10:in `block (2 levels) in func'")
++
++    def test_dup(self):
++        dup = self.frame.dup()
++        self.assertEqual(dup.function_name,
++            self.frame.function_name)
++
++        dup.function_name = 'other'
++        self.assertNotEqual(dup.function_name,
++            self.frame.function_name)
++
++    def test_cmp(self):
++        dup = self.frame.dup()
++        self.assertTrue(dup.equals(dup))
++        self.assertTrue(dup.equals(self.frame))
++        self.assertNotEqual(id(dup), id(self.frame))
++        dup.function_name = 'another'
++        self.assertFalse(dup.equals(self.frame))
++
++    def test_getset(self):
++        self.assertGetSetCorrect(self.frame, 'file_name', '/usr/share/ruby/vendor_ruby/will_crash.rb', 'java.rs')
++        self.assertGetSetCorrect(self.frame, 'file_line', 10, 6667)
++        self.assertGetSetCorrect(self.frame, 'special_function', False, True)
++        self.assertGetSetCorrect(self.frame, 'function_name', 'func', 'iiiiii')
++        self.assertGetSetCorrect(self.frame, 'block_level', 2, 666)
++        self.assertGetSetCorrect(self.frame, 'rescue_level', 0, 9000)
++
++    def test_hash(self):
++        self.assertHashable(self.frame)
++
++if __name__ == '__main__':
++    unittest.main()
+diff --git a/tests/python_bindings.at b/tests/python_bindings.at
+index a5e19c3..906f561 100644
+--- a/tests/python_bindings.at
++++ b/tests/python_bindings.at
+@@ -12,5 +12,6 @@ AT_TEST_PYTHON([koops])
+ AT_TEST_PYTHON([python])
+ AT_TEST_PYTHON([java])
+ AT_TEST_PYTHON([core])
++AT_TEST_PYTHON([ruby])
+ AT_TEST_PYTHON([metrics])
+ AT_TEST_PYTHON([report])
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-python-exceptions.patch b/SOURCES/satyr-0.13-python-exceptions.patch
new file mode 100644
index 0000000..f028fea
--- /dev/null
+++ b/SOURCES/satyr-0.13-python-exceptions.patch
@@ -0,0 +1,273 @@
+From 7fa846eea1f9f1a541ec061f272d4f82d4aee428 Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Wed, 25 Jun 2014 17:28:00 +0200
+Subject: [SATYR PATCH 3/6] Fix parsing of invalid syntax Python exceptions
+
+Closes #168
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+
+mmilata: commit msg typo
+---
+ lib/python_frame.c                 | 56 +++++++++++++++++++------------
+ lib/python_stacktrace.c            | 69 ++++++++++++++++++++++++++++++++++----
+ tests/python/python.py             | 38 +++++++++++++++++++++
+ tests/python_stacktraces/python-04 |  6 ++++
+ tests/python_stacktraces/python-05 | 16 +++++++++
+ 5 files changed, 157 insertions(+), 28 deletions(-)
+ create mode 100644 tests/python_stacktraces/python-04
+ create mode 100644 tests/python_stacktraces/python-05
+
+diff --git a/lib/python_frame.c b/lib/python_frame.c
+index b62628f..b0540fe 100644
+--- a/lib/python_frame.c
++++ b/lib/python_frame.c
+@@ -260,33 +260,45 @@ sr_python_frame_parse(const char **input,
+ 
+     if (0 == sr_skip_string(&local_input, ", in "))
+     {
+-        location->message = sr_asprintf("Function name separator not found.");
+-        return NULL;
+-    }
+-
+-    location->column += strlen(", in ");
++        if (local_input[0] != '\n')
++        {
++            location->message = sr_asprintf("Function name separator not found.");
++            return NULL;
++        }
+ 
+-    /* Parse function name */
+-    if (!sr_parse_char_cspan(&local_input, "\n", &frame->function_name))
++        /* The last frame of SyntaxError stack trace does not have
++         * function name on its line. For the sake of simplicity, we will
++         * believe that we are dealing with such a frame now.
++         */
++        frame->function_name = sr_strdup("syntax");
++        frame->special_function = true;
++    }
++    else
+     {
+-        sr_python_frame_free(frame);
+-        location->message = sr_asprintf("Unable to find the newline character "
+-                "identifying the end of function name.");
++        location->column += strlen(", in ");
+ 
+-        return NULL;
+-    }
++        /* Parse function name */
++        if (!sr_parse_char_cspan(&local_input, "\n", &frame->function_name))
++        {
++            sr_python_frame_free(frame);
++            location->message = sr_asprintf("Unable to find the newline character "
++                    "identifying the end of function name.");
+ 
+-    location->column += strlen(frame->function_name);
++            return NULL;
++        }
+ 
+-    if (strlen(frame->function_name) > 0 &&
+-        frame->function_name[0] == '<' &&
+-        frame->function_name[strlen(frame->function_name)-1] == '>')
+-    {
+-        frame->special_function = true;
+-        frame->function_name[strlen(frame->function_name)-1] = '\0';
+-        char *inside = sr_strdup(frame->function_name + 1);
+-        free(frame->function_name);
+-        frame->function_name = inside;
++        location->column += strlen(frame->function_name);
++
++        if (strlen(frame->function_name) > 0 &&
++            frame->function_name[0] == '<' &&
++            frame->function_name[strlen(frame->function_name)-1] == '>')
++        {
++            frame->special_function = true;
++            frame->function_name[strlen(frame->function_name)-1] = '\0';
++            char *inside = sr_strdup(frame->function_name + 1);
++            free(frame->function_name);
++            frame->function_name = inside;
++        }
+     }
+ 
+     sr_skip_char(&local_input, '\n');
+diff --git a/lib/python_stacktrace.c b/lib/python_stacktrace.c
+index 3dc0a75..99aa52c 100644
+--- a/lib/python_stacktrace.c
++++ b/lib/python_stacktrace.c
+@@ -29,6 +29,7 @@
+ #include "generic_thread.h"
+ #include "internal_utils.h"
+ #include <stdio.h>
++#include <stdlib.h>
+ #include <string.h>
+ #include <inttypes.h>
+ 
+@@ -137,13 +138,48 @@ sr_python_stacktrace_parse(const char **input,
+ 
+     if (!local_input)
+     {
+-        location->message = "Traceback header not found.";
+-        return NULL;
+-    }
++        /* SyntaxError stack trace of an exception thrown in the executed file
++         * conforms to the following template:
++         * invalid syntax ($file, line $number)
++         *
++         *    File "$file", line $number
++         *       $code
++         *         ^
++         * SyntaxError: invalid syntax
++         *
++         * for exceptions thrown from imported files, the stack trace has the
++         * regular form, except the last frame has no function name and is
++         * followed by the pointer line (^).
++         */
++        HEADER = "invalid syntax (",
++        local_input = sr_strstr_location(*input,
++                                     HEADER,
++                                     &location->line,
++                                     &location->column);
+ 
+-    local_input += strlen(HEADER);
+-    location->line += 2;
+-    location->column = 0;
++        if (!local_input)
++        {
++            location->message = "Traceback header not found.";
++            return NULL;
++        }
++
++        local_input = sr_strstr_location(local_input,
++                           "  File \"",
++                           &location->line,
++                           &location->column);
++
++        if (!local_input)
++        {
++            location->message = "Frame with invalid line not found.";
++            return NULL;
++        }
++    }
++    else
++    {
++        local_input += strlen(HEADER);
++        location->line += 2;
++        location->column = 0;
++    }
+ 
+     struct sr_python_stacktrace *stacktrace = sr_python_stacktrace_new();
+ 
+@@ -173,6 +209,27 @@ sr_python_stacktrace_parse(const char **input,
+         return NULL;
+     }
+ 
++    bool invalid_syntax_pointer = true;
++    const char *tmp_input = local_input;
++    while (*tmp_input != '\n' && *tmp_input != '\0')
++    {
++        if (*tmp_input != ' ' && *tmp_input != '^')
++        {
++            invalid_syntax_pointer = false;
++            break;
++        }
++        ++tmp_input;
++    }
++
++    if (invalid_syntax_pointer)
++    {
++        /* Skip line "   ^" pointing to the invalid code */
++        sr_skip_char_cspan(&local_input, "\n");
++        ++local_input;
++        ++location->line;
++        location->column = 1;
++    }
++
+     /* Parse exception name. */
+     sr_parse_char_cspan(&local_input,
+                         ":\n",
+diff --git a/tests/python/python.py b/tests/python/python.py
+index 146ca01..9044200 100755
+--- a/tests/python/python.py
++++ b/tests/python/python.py
+@@ -143,6 +143,44 @@ class TestPythonStacktrace(BindingsTestCase):
+     def test_hash(self):
+         self.assertHashable(self.trace)
+ 
++    def test_invalid_syntax_current_file(self):
++        trace = load_input_contents('../python_stacktraces/python-04')
++        trace = satyr.PythonStacktrace(trace)
++
++        self.assertEqual(len(trace.frames), 1)
++        self.assertEqual(trace.exception_name, 'SyntaxError')
++
++        f = trace.frames[0]
++        self.assertEqual(f.file_name, '/usr/bin/python3-mako-render')
++        self.assertEqual(f.function_name, "syntax")
++        self.assertEqual(f.file_line, 43)
++        self.assertEqual(f.line_contents, 'print render(data, kw)')
++        self.assertTrue(f.special_function)
++        self.assertFalse(f.special_file)
++
++    def test_invalid_syntax_imported_file(self):
++        trace = load_input_contents('../python_stacktraces/python-05')
++        trace = satyr.PythonStacktrace(trace)
++
++        self.assertEqual(len(trace.frames), 2)
++        self.assertEqual(trace.exception_name, 'SyntaxError')
++
++        f = trace.frames[1]
++        self.assertEqual(f.file_name, '/usr/bin/will_python_raise')
++        self.assertEqual(f.function_name, "module")
++        self.assertEqual(f.file_line, 2)
++        self.assertEqual(f.line_contents, 'import report')
++        self.assertTrue(f.special_function)
++        self.assertFalse(f.special_file)
++
++        f = trace.frames[0]
++        self.assertEqual(f.file_name, '/usr/lib64/python2.7/site-packages/report/__init__.py')
++        self.assertEqual(f.function_name, "syntax")
++        self.assertEqual(f.file_line, 15)
++        self.assertEqual(f.line_contents, 'def foo(:')
++        self.assertTrue(f.special_function)
++        self.assertFalse(f.special_file)
++
+ class TestPythonFrame(BindingsTestCase):
+     def setUp(self):
+         self.frame = satyr.PythonStacktrace(contents).frames[-1]
+diff --git a/tests/python_stacktraces/python-04 b/tests/python_stacktraces/python-04
+new file mode 100644
+index 0000000..91c3a3d
+--- /dev/null
++++ b/tests/python_stacktraces/python-04
+@@ -0,0 +1,6 @@
++invalid syntax (python3-mako-render, line 43)
++
++  File "/usr/bin/python3-mako-render", line 43
++    print render(data, kw)
++               ^
++SyntaxError: invalid syntax
+diff --git a/tests/python_stacktraces/python-05 b/tests/python_stacktraces/python-05
+new file mode 100644
+index 0000000..79d1b8d
+--- /dev/null
++++ b/tests/python_stacktraces/python-05
+@@ -0,0 +1,16 @@
++will_python_raise:2:<module>:  File "/usr/lib64/python2.7/site-packages/report/__init__.py", line 15
++
++Traceback (most recent call last):
++  File "/usr/bin/will_python_raise", line 2, in <module>
++    import report
++  File "/usr/lib64/python2.7/site-packages/report/__init__.py", line 15
++    def foo(:
++            ^
++SyntaxError: invalid syntax
++
++Local variables in innermost frame:
++__builtins__: <module '__builtin__' (built-in)>
++__name__: '__main__'
++__file__: '/usr/bin/will_python_raise'
++__doc__: None
++__package__: None
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-static-analyzer-bugs.patch b/SOURCES/satyr-0.13-static-analyzer-bugs.patch
new file mode 100644
index 0000000..af1dd34
--- /dev/null
+++ b/SOURCES/satyr-0.13-static-analyzer-bugs.patch
@@ -0,0 +1,137 @@
+From 5aab1d42916b93e593db632479a800b1de05d169 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 17 Feb 2014 12:28:04 +0100
+Subject: [SATYR PATCH 1/6] Fix minor issues found by static analyzers
+
+Avoid possible NULL dereferences of error_msg
+
+Fixes #155.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+
+Fix insecure string formatting
+
+core_unwind_elfutils.c: In function 'sr_parse_coredump':
+core_unwind_elfutils.c:169:13: error: format not a string literal and no format arguments [-Werror=format-security]
+             set_error(thread_arg.error_msg);
+             ^
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/Makefile.am             |  2 +-
+ lib/core_unwind.c           |  5 +++--
+ lib/core_unwind_elfutils.c  | 27 +++++++++++++--------------
+ lib/core_unwind_libunwind.c |  2 +-
+ 4 files changed, 18 insertions(+), 18 deletions(-)
+
+diff --git a/lib/Makefile.am b/lib/Makefile.am
+index 73fffe2..f798347 100644
+--- a/lib/Makefile.am
++++ b/lib/Makefile.am
+@@ -62,7 +62,7 @@ libsatyr_conv_la_SOURCES = \
+ 	unstrip.c \
+ 	utils.c
+ 
+-libsatyr_conv_la_CFLAGS = -Wall -std=gnu99 -D_GNU_SOURCE -I$(top_srcdir)/include $(GLIB_CFLAGS)
++libsatyr_conv_la_CFLAGS = -Wall -Wformat=2 -std=gnu99 -D_GNU_SOURCE -I$(top_srcdir)/include $(GLIB_CFLAGS)
+ libsatyr_conv_la_LDFLAGS = $(GLIB_LIBS)
+ 
+ if HAVE_LIBOPCODES
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index 7910254..8b7cc22 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -227,9 +227,10 @@ open_coredump(const char *elf_file, const char *exe_file, char **error_msg)
+     }
+     ch->segments = head;
+ 
+-    if (!*error_msg && !head)
++    if (!head)
+     {
+-        set_error("No segments found in coredump '%s'", elf_file);
++        if (error_msg && !*error_msg)
++            set_error("No segments found in coredump '%s'", elf_file);
+         goto fail_dwfl;
+     }
+ 
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index a8d8b3f..6b904c7 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -137,20 +137,20 @@ sr_parse_coredump(const char *core_file,
+         *error_msg = NULL;
+ 
+     struct core_handle *ch = open_coredump(core_file, exe_file, error_msg);
+-    if (*error_msg)
+-        return NULL;
++    if (!ch)
++        goto fail;
+ 
+     if (dwfl_core_file_attach(ch->dwfl, ch->eh) < 0)
+     {
+         set_error_dwfl("dwfl_core_file_attach");
+-        goto fail_destroy_handle;
++        goto fail;
+     }
+ 
+     stacktrace = sr_core_stacktrace_new();
+     if (!stacktrace)
+     {
+         set_error("Failed to initialize stacktrace memory");
+-        goto fail_destroy_handle;
++        goto fail;
+     }
+ 
+     struct thread_callback_arg thread_arg =
+@@ -165,11 +165,16 @@ sr_parse_coredump(const char *core_file,
+         if (ret == -1)
+             set_error_dwfl("dwfl_getthreads");
+         else if (ret == DWARF_CB_ABORT)
+-            *error_msg = thread_arg.error_msg;
++        {
++            set_error("%s", thread_arg.error_msg);
++            free(thread_arg.error_msg);
++        }
+         else
+-            *error_msg = sr_strdup("Unknown error in dwfl_getthreads");
++            set_error("Unknown error in dwfl_getthreads");
+ 
+-        goto fail_destroy_trace;
++        sr_core_stacktrace_free(stacktrace);
++        stacktrace = NULL;
++        goto fail;
+     }
+ 
+     stacktrace->executable = sr_strdup(exe_file);
+@@ -177,13 +182,7 @@ sr_parse_coredump(const char *core_file,
+     /* FIXME: is this the best we can do? */
+     stacktrace->crash_thread = stacktrace->threads;
+ 
+-fail_destroy_trace:
+-    if (*error_msg)
+-    {
+-        sr_core_stacktrace_free(stacktrace);
+-        stacktrace = NULL;
+-    }
+-fail_destroy_handle:
++fail:
+     core_handle_free(ch);
+     return stacktrace;
+ }
+diff --git a/lib/core_unwind_libunwind.c b/lib/core_unwind_libunwind.c
+index 966a5b9..b45e2ad 100644
+--- a/lib/core_unwind_libunwind.c
++++ b/lib/core_unwind_libunwind.c
+@@ -99,7 +99,7 @@ unwind_thread(struct UCD_info *ui,
+         }
+     }
+ 
+-    if (!error_msg && !trace)
++    if (error_msg && !*error_msg && !trace)
+     {
+         set_error("No frames found for thread %d", thread_no);
+     }
+-- 
+1.9.3
+
diff --git a/SOURCES/satyr-0.13-support-unwinding-from-core-hook.patch b/SOURCES/satyr-0.13-support-unwinding-from-core-hook.patch
new file mode 100644
index 0000000..56a3060
--- /dev/null
+++ b/SOURCES/satyr-0.13-support-unwinding-from-core-hook.patch
@@ -0,0 +1,308 @@
+From 91cdbeb45d11cfd1dd62afe50f80a7c5d4342f5b Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 24 Nov 2014 14:36:14 +0100
+Subject: [SATYR PATCH] unwind: support unwinding from core hook
+
+In-memory unwinding of dying processes, related to abrt/abrt#gh829. The
+implementation mostly mirrors code in src/stack.c of elfutils repo
+(jankratochvil/corepattern branch).
+
+Related to abrt/abrt#829.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ include/abrt.h             |   8 +++
+ include/core/unwind.h      |  18 ++++++
+ lib/abrt.c                 |  35 ++++++++++++
+ lib/core_unwind.c          |  14 +++++
+ lib/core_unwind_elfutils.c | 136 +++++++++++++++++++++++++++++++++++++++++++++
+ 5 files changed, 211 insertions(+)
+
+diff --git a/include/abrt.h b/include/abrt.h
+index 0a8f5ac..5116446 100644
+--- a/include/abrt.h
++++ b/include/abrt.h
+@@ -26,6 +26,7 @@ extern "C" {
+ 
+ #include "report_type.h"
+ #include <stdbool.h>
++#include <sys/types.h>
+ 
+ bool
+ sr_abrt_print_report_from_dir(const char *directory,
+@@ -43,6 +44,13 @@ sr_abrt_create_core_stacktrace_from_gdb(const char *directory,
+                                         bool hash_fingerprints,
+                                         char **error_message);
+ 
++bool
++sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
++                                              pid_t thread_id,
++                                              const char *executable,
++                                              int signum,
++                                              char **error_message);
++
+ struct sr_rpm_package *
+ sr_abrt_parse_dso_list(const char *text);
+ 
+diff --git a/include/core/unwind.h b/include/core/unwind.h
+index 8ab9d52..b128c76 100644
+--- a/include/core/unwind.h
++++ b/include/core/unwind.h
+@@ -24,6 +24,8 @@
+ extern "C" {
+ #endif
+ 
++#include <sys/types.h>
++
+ struct sr_core_stacktrace;
+ struct sr_gdb_stacktrace;
+ 
+@@ -38,6 +40,22 @@ sr_core_stacktrace_from_gdb(const char *gdb_output,
+                             const char *executable_filename,
+                             char **error_message);
+ 
++/* This function can be used to unwind stack of live ("dying") process, invoked
++ * from the core dump hook (/proc/sys/kernel/core_pattern).
++ *
++ * Beware:
++ *
++ * - It can only unwind one thread of the process, the thread that caused the
++ *   terminating signal to be sent. You must supply that thread's tid.
++ * - The function calls close() on stdin, meaning that in the core handler you
++ *   cannot access the core image after calling this function.
++ */
++struct sr_core_stacktrace *
++sr_core_stacktrace_from_core_hook(pid_t thread_id,
++                                  const char *executable_filename,
++                                  int signum,
++                                  char **error_message);
++
+ #ifdef __cplusplus
+ }
+ #endif
+diff --git a/lib/abrt.c b/lib/abrt.c
+index 39bc45d..585fdfb 100644
+--- a/lib/abrt.c
++++ b/lib/abrt.c
+@@ -138,6 +138,41 @@ sr_abrt_create_core_stacktrace(const char *directory,
+                                   error_message);
+ }
+ 
++bool
++sr_abrt_create_core_stacktrace_from_core_hook(const char *directory,
++                                              pid_t thread_id,
++                                              const char *executable,
++                                              int signum,
++                                              char **error_message)
++{
++
++    struct sr_core_stacktrace *core_stacktrace;
++    core_stacktrace = sr_core_stacktrace_from_core_hook(thread_id, executable,
++                                                        signum, error_message);
++
++    if (!core_stacktrace)
++        return false;
++
++    fulfill_missing_values(core_stacktrace);
++
++    char *json = sr_core_stacktrace_to_json(core_stacktrace);
++
++    // Add newline to the end of core stacktrace file to make text
++    // editors happy.
++    json = sr_realloc(json, strlen(json) + 2);
++    strcat(json, "\n");
++
++    char *core_backtrace_filename = sr_build_path(directory, "core_backtrace", NULL);
++    bool success = sr_string_to_file(core_backtrace_filename,
++                                    json,
++                                    error_message);
++
++    free(core_backtrace_filename);
++    free(json);
++    sr_core_stacktrace_free(core_stacktrace);
++    return success;
++}
++
+ struct sr_rpm_package *
+ sr_abrt_parse_dso_list(const char *text)
+ {
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index 8b7cc22..7ea66da 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -52,6 +52,20 @@ sr_parse_coredump(const char *coredump_filename,
+ 
+ #endif /* !defined WITH_LIBDWFL && !defined WITH_LIBUNWIND */
+ 
++#if !defined WITH_LIBDWFL
++
++struct sr_core_stacktrace *
++sr_core_stacktrace_from_core_hook(pid_t thread_id,
++                                  const char *executable_filename,
++                                  int signum,
++                                  char **error_message);
++{
++    *error_message = sr_asprintf("satyr is built without live process unwind support");
++    return NULL;
++}
++
++#endif /* !defined WITH_LIBDWFL */
++
+ /* FIXME: is there another way to pass the executable name to the find_elf
+  * callback? */
+ const char *executable_file = NULL;
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index bbd4813..bea9b5f 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -28,6 +28,10 @@
+ 
+ #include <stdio.h>
+ #include <string.h>
++#include <unistd.h>
++#include <errno.h>
++#include <sys/ptrace.h>
++#include <sys/wait.h>
+ 
+ #define FRAME_LIMIT 256
+ 
+@@ -202,4 +206,136 @@ fail:
+     return stacktrace;
+ }
+ 
++struct sr_core_stacktrace *
++sr_core_stacktrace_from_core_hook(pid_t tid,
++                                  const char *executable,
++                                  int signum,
++                                  char **error_msg)
++{
++    struct sr_core_stacktrace *stacktrace = NULL;
++
++    /* Initialize error_msg to 'no error'. */
++    if (error_msg)
++        *error_msg = NULL;
++
++    const Dwfl_Callbacks proc_cb =
++    {
++        .find_elf = dwfl_linux_proc_find_elf,
++        .find_debuginfo = find_debuginfo_none,
++    };
++
++    Dwfl *dwfl = dwfl_begin(&proc_cb);
++
++    if (dwfl_linux_proc_report(dwfl, tid) != 0)
++    {
++        set_error_dwfl("dwfl_linux_proc_report");
++        goto fail;
++    }
++
++    if (dwfl_report_end(dwfl, NULL, NULL) != 0)
++    {
++        set_error_dwfl("dwfl_report_end");
++        goto fail;
++    }
++
++    if (ptrace(PTRACE_SEIZE, tid, NULL, (void *)(uintptr_t)PTRACE_O_TRACEEXIT) != 0)
++    {
++        set_error("PTRACE_SEIZE (tid %u) failed: %s", (unsigned)tid, strerror(errno));
++        goto fail;
++    }
++
++    if (close(STDIN_FILENO) != 0)
++    {
++        set_error("Failed to close stdin: %s", strerror(errno));
++        goto fail;
++    }
++
++    int status;
++    pid_t got = waitpid(tid, &status, 0);
++    if (got == -1)
++    {
++        set_error("waitpid failed: %s", strerror(errno));
++        goto fail;
++    }
++
++    if (got != tid)
++    {
++        set_error("waitpid returned %u but %u was expected", (unsigned)got, (unsigned)tid);
++        goto fail;
++    }
++
++    if (!WIFSTOPPED(status))
++    {
++        set_error("waitpid returned 0x%x but WIFSTOPPED was expected", status);
++        goto fail;
++    }
++
++    if ((status >> 8) != (SIGTRAP | (PTRACE_EVENT_EXIT << 8)))
++    {
++        set_error("waitpid returned 0x%x but (status >> 8) == "
++                  "(SIGTRAP | (PTRACE_EVENT_EXIT << 8)) was expected", status);
++        goto fail;
++    }
++
++    if (dwfl_linux_proc_attach(dwfl, tid, true) != 0)
++    {
++        set_error_dwfl("dwfl_linux_proc_attach");
++        goto fail;
++    }
++
++    stacktrace = sr_core_stacktrace_new();
++    if (!stacktrace)
++    {
++        set_error("Failed to initialize stacktrace memory");
++        goto fail;
++    }
++
++    stacktrace->threads = sr_core_thread_new();
++    if (!stacktrace->threads)
++    {
++        set_error("Failed to initialize thread memory");
++        sr_core_stacktrace_free(stacktrace);
++        stacktrace = 0;
++        goto fail;
++    }
++    stacktrace->threads->id = tid;
++
++    struct frame_callback_arg frame_arg =
++    {
++        .frames_tail = &(stacktrace->threads->frames),
++        .error_msg = NULL,
++        .nframes = 0
++    };
++
++    int ret = dwfl_getthread_frames(dwfl, tid, frame_callback, &frame_arg);
++    if (ret != 0 && ret != CB_STOP_UNWIND)
++    {
++        if (ret == -1)
++            set_error_dwfl("dwfl_getthread_frames");
++        else if (ret == DWARF_CB_ABORT)
++        {
++            set_error("%s", frame_arg.error_msg);
++            free(frame_arg.error_msg);
++        }
++        else
++            set_error("Unknown error in dwfl_getthreads");
++
++        sr_core_stacktrace_free(stacktrace);
++        stacktrace = NULL;
++        goto fail;
++    }
++
++    truncate_long_thread(stacktrace->threads, &frame_arg);
++
++    if (executable)
++        stacktrace->executable = sr_strdup(executable);
++    if (signum > 0)
++        stacktrace->signal = (uint16_t)signum;
++    stacktrace->crash_thread = stacktrace->threads;
++
++fail:
++    dwfl_end(dwfl);
++    return stacktrace;
++}
++
+ #endif /* WITH_LIBDWFL */
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch b/SOURCES/satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch
new file mode 100644
index 0000000..3dd268c
--- /dev/null
+++ b/SOURCES/satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch
@@ -0,0 +1,44 @@
+From c1e7562809a1ea9955d43bc21fa2b2ac88d95ee5 Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Fri, 20 Feb 2015 15:50:32 +0100
+Subject: [PATCH] tests: fix failure on gcc5 on x86_64
+
+Resolves #1334604
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ tests/ruby_stacktrace.at | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/tests/ruby_stacktrace.at b/tests/ruby_stacktrace.at
+index 5a2d901..09d5638 100644
+--- a/tests/ruby_stacktrace.at
++++ b/tests/ruby_stacktrace.at
+@@ -261,6 +261,7 @@ main(void)
+ 
+ AT_TESTFUN([sr_ruby_stacktrace_from_json],
+ [[
++#include "stacktrace.h"
+ #include "ruby/stacktrace.h"
+ #include "ruby/frame.h"
+ #include "utils.h"
+@@ -268,13 +269,14 @@ AT_TESTFUN([sr_ruby_stacktrace_from_json],
+ #include <assert.h>
+ #include <stdlib.h>
+ #include <stdio.h>
++#include <string.h>
+ 
+ void
+ check(char *filename)
+ {
+   char *error_message = NULL;
+-  const char *file_contents = sr_file_to_string(filename, &error_message);
+-  const char *input = file_contents;
++  char *file_contents = sr_file_to_string(filename, &error_message);
++  char *input = file_contents;
+   struct sr_location location;
+   sr_location_init(&location);
+ 
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-testsuite-add-test-for-limit-frame-number-in-GDB-cor.patch b/SOURCES/satyr-0.13-testsuite-add-test-for-limit-frame-number-in-GDB-cor.patch
new file mode 100644
index 0000000..0cfcc61
--- /dev/null
+++ b/SOURCES/satyr-0.13-testsuite-add-test-for-limit-frame-number-in-GDB-cor.patch
@@ -0,0 +1,182 @@
+From 77243901ca72e4f49ed650eecfe501edfb2344df Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Tue, 30 Aug 2016 11:09:50 +0200
+Subject: [PATCH] testsuite: add test for limit frame number in GDB core
+ unwinder
+
+Related to #1260074
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ tests/core_stacktrace.at | 158 +++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 158 insertions(+)
+
+diff --git a/tests/core_stacktrace.at b/tests/core_stacktrace.at
+index f45e126..313d8b8 100644
+--- a/tests/core_stacktrace.at
++++ b/tests/core_stacktrace.at
+@@ -197,3 +197,161 @@ main(void)
+   return 0;
+ }
+ ]])
++
++## --------------------------------- ##
++## sr_core_stacktrace_from_gdb_limit ##
++## --------------------------------- ##
++
++AT_TESTFUN([sr_core_stacktrace_from_gdb_limit],
++[[
++#define _GNU_SOURCE
++#include "stacktrace.h"
++#include "core/stacktrace.h"
++#include "core/thread.h"
++#include "core/frame.h"
++#include "core/unwind.h"
++#include "strbuf.h"
++#include "utils.h"
++#include "report_type.h"
++#include <assert.h>
++#include <stdio.h>
++#include "internal_unwind.h"
++#include <stdlib.h>
++#include <unistd.h>
++#include <sys/types.h>
++#include <sys/wait.h>
++#include <errno.h>
++#include <err.h>
++#include <unistd.h>
++#include <fcntl.h>
++
++char *generate_coredump(int start)
++{
++
++  if (start)
++  {
++      return generate_coredump(--start);
++  }
++
++  const char* coredump_pattern = "/tmp/satyr.core";
++
++  char *mypid = NULL;
++  asprintf(&mypid, "%d", getpid());
++  if (mypid == NULL)
++      err(1, "asprintf");
++
++  pid_t pid = fork();
++  if (pid < 0)
++      err(1, "fork");
++  if (pid == 0)
++  {
++      char *args[] = { "gcore", "-o", (char *)coredump_pattern, mypid, NULL };
++      execv("/usr/bin/gcore", args);
++  }
++
++  int status;
++  int r;
++  while ((r = waitpid(pid, &status, 0)) < 0)
++  {
++      if (errno != EAGAIN)
++          err(1, "waitpid");
++  }
++
++  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
++      errx(1, "gcore failed");
++
++  return sr_asprintf("%s.%s", coredump_pattern, mypid);
++}
++struct sr_strbuf *
++get_backtrace(const char *core_file, const char *executable)
++{
++  unsigned i = 0;
++  char *args[25];
++  args[i++] = (char*)"/usr/bin/gdb";
++  args[i++] = (char*)"-batch";
++  args[i++] = (char*)"-iex";
++  args[i++] = (char*)"set debug-file-directory /";
++  args[i++] = (char*)"-ex";
++  args[i++] = sr_asprintf("file %s", executable);
++  args[i++] = (char*)"-ex";
++  args[i++] = sr_asprintf("core-file %s", core_file);
++  args[i++] = (char*)"-ex";
++  args[i++] = (char*)"thread apply all backtrace 1024 full";
++  args[i++] = (char*)"-ex";
++  args[i++] = (char*)"info sharedlib";
++  args[i++] = (char*)"-ex";
++  args[i++] = (char*)"print (char*)__abort_msg";
++  args[i++] = (char*)"-ex";
++  args[i++] = (char*)"print (char*)__glib_assert_msg";
++  args[i++] = (char*)"-ex";
++  args[i++] = (char*)"info all-registers";
++  args[i++] = (char*)"-ex";
++  args[i++] = (char*)"disassemble";
++  args[i++] = NULL;
++
++  int p[2];
++  int pipe_return = pipe(p);
++  if (pipe_return != 0)
++      err(1, "pipe");
++
++  pid_t pid = fork();
++  if (pid < 0)
++      err(1, "fork");
++  if (pid == 0) /* child */
++  {
++    close(p[0]);
++    dup2(p[1], 1);  // send stdout to the pipe
++    dup2(p[1], 2);  // send stderr to the pipe
++
++    execv(args[0], args);
++  }
++  close(p[1]);
++
++  struct sr_strbuf *gdb_buf = sr_strbuf_new();
++  sr_strbuf_init(gdb_buf);
++  char buf[256];
++  ssize_t size;
++  while((size = read(p[0], buf, sizeof(buf)-1)) > 0 && errno != EINTR)
++  {
++    buf[size] = '\0';
++    sr_strbuf_append_str(gdb_buf, (const char*) buf);
++  }
++
++  int status, r;
++  while ((r = waitpid(pid, &status, 0)) < 0)
++  {
++      if (errno != EAGAIN)
++          err(1, "waitpid");
++  }
++
++  return gdb_buf;
++}
++
++int
++main(int argc, char* argv[])
++{
++  char *coredump_path = generate_coredump(257);
++
++  struct sr_strbuf *gdb_output = get_backtrace(coredump_path, argv[0]);
++  assert(gdb_output);
++  assert(gdb_output->buf);
++
++  char *error_msg = NULL;
++  struct sr_core_stacktrace *core_stacktrace = sr_core_stacktrace_from_gdb(gdb_output->buf, coredump_path, argv[0], &error_msg);
++  assert(!error_msg);
++
++  struct sr_core_thread *threads = core_stacktrace->threads;
++  struct sr_core_frame *frames = threads->frames;
++  unsigned nframes = 0;
++  for(; frames; ++nframes, frames = frames->next)
++    ;
++
++  /* max num of frames is CORE_STACKTRACE_FRAME_LIMIT */
++  assert(nframes == CORE_STACKTRACE_FRAME_LIMIT);
++
++  free(coredump_path);
++  sr_strbuf_free(gdb_output);
++
++  return 0;
++}
++]])
+-- 
+2.17.1
+
diff --git a/SOURCES/satyr-0.13-unwind-minor-refactoring.patch b/SOURCES/satyr-0.13-unwind-minor-refactoring.patch
new file mode 100644
index 0000000..3c392cb
--- /dev/null
+++ b/SOURCES/satyr-0.13-unwind-minor-refactoring.patch
@@ -0,0 +1,94 @@
+From f8be518c5d8534f8724c752c6ad95b3e61460a7b Mon Sep 17 00:00:00 2001
+From: Martin Milata <mmilata@redhat.com>
+Date: Mon, 24 Nov 2014 14:34:09 +0100
+Subject: [PATCH 2/5] unwind: minor refactoring
+
+Related to abrt/abrt#829.
+
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+---
+ lib/core_unwind.c          |  2 +-
+ lib/core_unwind_elfutils.c | 24 +++++++++++++++---------
+ lib/internal_unwind.h      |  6 ++++++
+ 3 files changed, 22 insertions(+), 10 deletions(-)
+
+diff --git a/lib/core_unwind.c b/lib/core_unwind.c
+index 8b7cc22..48d5ca5 100644
+--- a/lib/core_unwind.c
++++ b/lib/core_unwind.c
+@@ -127,7 +127,7 @@ find_elf_core (Dwfl_Module *mod, void **userdata, const char *modname,
+ }
+ 
+ /* Do not use debuginfo files at all. */
+-static int
++int
+ find_debuginfo_none (Dwfl_Module *mod, void **userdata, const char *modname,
+                      GElf_Addr base, const char *file_name,
+                      const char *debuglink_file, GElf_Word debuglink_crc,
+diff --git a/lib/core_unwind_elfutils.c b/lib/core_unwind_elfutils.c
+index bbd4813..8351ebf 100644
+--- a/lib/core_unwind_elfutils.c
++++ b/lib/core_unwind_elfutils.c
+@@ -77,6 +77,19 @@ frame_callback(Dwfl_Frame *frame, void *data)
+     return DWARF_CB_OK;
+ }
+ 
++static void
++truncate_long_thread(struct sr_core_thread *thread, struct frame_callback_arg *frame_arg)
++{
++    /* Truncate the stacktrace to FRAME_LIMIT least recent frames. */
++    while (thread->frames && frame_arg->nframes > FRAME_LIMIT)
++    {
++        struct sr_core_frame *old_frame = thread->frames;
++        thread->frames = old_frame->next;
++        sr_core_frame_free(old_frame);
++        frame_arg->nframes--;
++    }
++}
++
+ static int
+ unwind_thread(Dwfl_Thread *thread, void *data)
+ {
+@@ -86,7 +99,7 @@ unwind_thread(Dwfl_Thread *thread, void *data)
+     struct sr_core_thread *result = sr_core_thread_new();
+     if (!result)
+     {
+-        set_error("Failed to initialize stacktrace memory");
++        set_error("Failed to initialize thread memory");
+         return DWARF_CB_ABORT;
+     }
+     result->id = (int64_t)dwfl_thread_tid(thread);
+@@ -121,14 +134,7 @@ unwind_thread(Dwfl_Thread *thread, void *data)
+         goto abort;
+     }
+ 
+-    /* Truncate the stacktrace to FRAME_LIMIT least recent frames. */
+-    while (result->frames && frame_arg.nframes > FRAME_LIMIT)
+-    {
+-        struct sr_core_frame *old_frame = result->frames;
+-        result->frames = old_frame->next;
+-        sr_core_frame_free(old_frame);
+-        frame_arg.nframes--;
+-    }
++    truncate_long_thread(result, &frame_arg);
+ 
+     *thread_arg->threads_tail = result;
+     thread_arg->threads_tail = &result->next;
+diff --git a/lib/internal_unwind.h b/lib/internal_unwind.h
+index 2c9abb7..84261c6 100644
+--- a/lib/internal_unwind.h
++++ b/lib/internal_unwind.h
+@@ -96,4 +96,10 @@ resolve_frame(Dwfl *dwfl, Dwarf_Addr ip, bool minus_one);
+ short
+ get_signal_number(Elf *e, const char *elf_file);
+ 
++int
++find_debuginfo_none (Dwfl_Module *mod, void **userdata, const char *modname,
++                     GElf_Addr base, const char *file_name,
++                     const char *debuglink_file, GElf_Word debuglink_crc,
++                     char **debuginfo_file_name);
++
+ #endif /* SATYR_INTERNAL_UNWIND_H */
+-- 
+2.4.3
+
diff --git a/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch b/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch
new file mode 100644
index 0000000..735c3ca
--- /dev/null
+++ b/SOURCES/satyr-0.13-unwinder-refresh-config-h.patch
@@ -0,0 +1,46 @@
+From 7351e5b5ad04bd840e75fafb8ab19d4e0c90d717 Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Fri, 13 May 2016 14:42:23 +0200
+Subject: [PATCH] unwinder refresh config h
+
+---
+ configure       | 6 +++---
+ lib/config.h.in | 4 ++--
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/configure b/configure
+index 2418533..f79cfeb 100755
+--- a/configure
++++ b/configure
+@@ -13261,10 +13261,10 @@ _ACEOF
+ 
+ fi
+ 
+-ac_fn_c_check_func "$LINENO" "dwfl_frame_state_core" "ac_cv_func_dwfl_frame_state_core"
+-if test "x$ac_cv_func_dwfl_frame_state_core" = xyes; then :
++ac_fn_c_check_func "$LINENO" "dwfl_getthreads" "ac_cv_func_dwfl_getthreads"
++if test "x$ac_cv_func_dwfl_getthreads" = xyes; then :
+ 
+-$as_echo "#define HAVE_DWFL_FRAME_STATE_CORE /**/" >>confdefs.h
++$as_echo "#define HAVE_DWFL_NEXT_THREAD /**/" >>confdefs.h
+ 
+ fi
+ 
+diff --git a/lib/config.h.in b/lib/config.h.in
+index b5c34c6..b7b6255 100644
+--- a/lib/config.h.in
++++ b/lib/config.h.in
+@@ -6,8 +6,8 @@
+ /* Define to 1 if you have the <dwarf.h> header file. */
+ #undef HAVE_DWARF_H
+ 
+-/* Have function dwfl_frame_state_core for coredump unwinding */
+-#undef HAVE_DWFL_FRAME_STATE_CORE
++/* Have function dwfl_getthreads for coredump unwinding */
++#undef HAVE_DWFL_NEXT_THREAD
+ 
+ /* Define to 1 if you have the <elfutils/libdwfl.h> header file. */
+ #undef HAVE_ELFUTILS_LIBDWFL_H
+-- 
+1.8.3.1
+
diff --git a/SOURCES/satyr-0.13-ureport-auth-support.patch b/SOURCES/satyr-0.13-ureport-auth-support.patch
new file mode 100644
index 0000000..220ac73
--- /dev/null
+++ b/SOURCES/satyr-0.13-ureport-auth-support.patch
@@ -0,0 +1,565 @@
+From 3e11b3c67bc31662810f72571311d7d7580e3f0a Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Mon, 18 Aug 2014 20:39:34 +0200
+Subject: [SATYR PATCH 6/6] Add authentication data support in uReport
+
+Support authentication data in uReport
+
+We need this for 'machine id' element which should be present only if
+users wishes to it.
+
+The authentication entries are string based, key value pairs (JSON
+string elements).
+
+The entries are enclosed in an envelope object called 'auth'.
+
+Example:
+
+"auth" : {   "machineid" : "deadbeaf"
+         }
+
+Fixes #171
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+
+mmilata: s/custom/auth/g, python bindings
+
+tests: add a test for uReport auth data
+
+Related to #171
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+Signed-off-by: Martin Milata <mmilata@redhat.com>
+
+mmilata: python test
+
+ureport: fix indentation of 'auth' key in JSON
+
+Fixes a bug in commit 189f7adbba9c38bd434c30e7d0c07b546c373312
+
+Related to #171
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ include/report.h                |  17 ++++++
+ lib/internal_utils.h            |  12 +++++
+ lib/json.c                      |  26 ++++++++++
+ lib/report.c                    |  82 +++++++++++++++++++++++++++++
+ python/py_report.c              |  30 +++++++++++
+ python/py_report.h              |   2 +
+ tests/json_files/ureport-1-auth | 112 ++++++++++++++++++++++++++++++++++++++++
+ tests/python/report.py          |   6 +++
+ tests/report.at                 |  81 +++++++++++++++++++++++++++++
+ 9 files changed, 368 insertions(+)
+ create mode 100644 tests/json_files/ureport-1-auth
+
+diff --git a/include/report.h b/include/report.h
+index 424b730..025d8f8 100644
+--- a/include/report.h
++++ b/include/report.h
+@@ -31,6 +31,13 @@ extern "C" {
+ struct sr_json_value;
+ struct sr_stacktrace;
+ 
++struct sr_report_custom_entry
++{
++    char *key;
++    char *value;
++    struct sr_report_custom_entry *next;
++};
++
+ struct sr_report
+ {
+     uint32_t report_version;
+@@ -49,6 +56,8 @@ struct sr_report
+     struct sr_rpm_package *rpm_packages;
+ 
+     struct sr_stacktrace *stacktrace;
++
++    struct sr_report_custom_entry *auth_entries;
+ };
+ 
+ struct sr_report *
+@@ -60,6 +69,14 @@ sr_report_init(struct sr_report *report);
+ void
+ sr_report_free(struct sr_report *report);
+ 
++
++/* @brief Adds a new entry to 'auth' object
++ *
++ * The implementation is LIFO. The resulting list is in reversed.
++ */
++void
++sr_report_add_auth(struct sr_report *report, const char *key, const char *value);
++
+ char *
+ sr_report_to_json(struct sr_report *report);
+ 
+diff --git a/lib/internal_utils.h b/lib/internal_utils.h
+index 20f65de..3a10520 100644
+--- a/lib/internal_utils.h
++++ b/lib/internal_utils.h
+@@ -130,4 +130,16 @@ json_read_bool(struct sr_json_value *object, const char *key_name, bool *dest,
+         abort();                                                                      \
+     }
+ 
++/* returns the number of object's children */
++unsigned
++json_object_children_count(struct sr_json_value *object);
++
++/* returns the child_noth child and passes its name in child_name arg */
++struct sr_json_value *
++json_object_get_child(struct sr_json_value *object, unsigned child_no, const char **child_name);
++
++/* returns string's value */
++const char *
++json_string_get_value(struct sr_json_value *object);
++
+ #endif
+diff --git a/lib/json.c b/lib/json.c
+index 35b7e6b..d777973 100644
+--- a/lib/json.c
++++ b/lib/json.c
+@@ -787,6 +787,32 @@ json_element(struct sr_json_value *object, const char *key_name)
+     return NULL;
+ }
+ 
++unsigned
++json_object_children_count(struct sr_json_value *object)
++{
++    assert(object->type == SR_JSON_OBJECT);
++
++    return object->u.object.length;
++}
++
++struct sr_json_value *
++json_object_get_child(struct sr_json_value *object, unsigned child_no, const char **child_name)
++{
++    assert(object->type == SR_JSON_OBJECT);
++    assert(child_no < object->u.object.length);
++
++    *child_name = object->u.object.values[child_no].name;
++    return object->u.object.values[child_no].value;
++}
++
++const char *
++json_string_get_value(struct sr_json_value *object)
++{
++    assert(object->type == SR_JSON_STRING);
++
++    return object->u.string.ptr;
++}
++
+ #define DEFINE_JSON_READ(name, c_type, json_type, json_member, conversion)                       \
+     bool                                                                                         \
+     name(struct sr_json_value *object, const char *key_name, c_type *dest, char **error_message) \
+diff --git a/lib/report.c b/lib/report.c
+index a6ecae4..411df64 100644
+--- a/lib/report.c
++++ b/lib/report.c
+@@ -66,6 +66,7 @@ sr_report_init(struct sr_report *report)
+     report->component_name = NULL;
+     report->rpm_packages = NULL;
+     report->stacktrace = NULL;
++    report->auth_entries = NULL;
+ }
+ 
+ void
+@@ -75,9 +76,36 @@ sr_report_free(struct sr_report *report)
+     sr_operating_system_free(report->operating_system);
+     sr_rpm_package_free(report->rpm_packages, true);
+     sr_stacktrace_free(report->stacktrace);
++
++    struct sr_report_custom_entry *iter = report->auth_entries;
++    while (iter)
++    {
++        struct sr_report_custom_entry *tmp = iter->next;
++
++        free(iter->value);
++        free(iter->key);
++        free(iter);
++
++        iter = tmp;
++    }
++
+     free(report);
+ }
+ 
++void
++sr_report_add_auth(struct sr_report *report, const char *key, const char *value)
++{
++    struct sr_report_custom_entry *new_entry = sr_malloc(sizeof(*new_entry));
++    new_entry->key = sr_strdup(key);
++    new_entry->value = sr_strdup(value);
++
++    /* prepend the new value
++     * it is much faster and easier
++     * we can do it because we do not to preserve the order(?) */
++    new_entry->next = report->auth_entries;
++    report->auth_entries = new_entry;
++}
++
+ /* The object has to be non-empty, i.e. contain at least one key-value pair. */
+ static void
+ dismantle_object(char *obj)
+@@ -221,6 +249,34 @@ sr_report_to_json(struct sr_report *report)
+         free(rpms_str_indented);
+     }
+ 
++    /* Custom entries.
++     *    "auth" : {   "foo": "blah"
++     *             ,   "one": "two"
++     *             }
++     */
++    struct sr_report_custom_entry *iter = report->auth_entries;
++    if (iter)
++    {
++        sr_strbuf_append_strf(strbuf, ",   \"auth\": {   ");
++        sr_json_append_escaped(strbuf, iter->key);
++        sr_strbuf_append_str(strbuf, ": ");
++        sr_json_append_escaped(strbuf, iter->value);
++        sr_strbuf_append_str(strbuf, "\n");
++
++        /* the first entry is prefix with '{', see lines above */
++        iter = iter->next;
++        while (iter)
++        {
++            sr_strbuf_append_strf(strbuf, "            ,   ");
++            sr_json_append_escaped(strbuf, iter->key);
++            sr_strbuf_append_str(strbuf, ": ");
++            sr_json_append_escaped(strbuf, iter->value);
++            sr_strbuf_append_str(strbuf, "\n");
++            iter = iter->next;
++        }
++        sr_strbuf_append_str(strbuf, "            } ");
++    }
++
+     sr_strbuf_append_str(strbuf, "}");
+     return sr_strbuf_free_nobuf(strbuf);
+ }
+@@ -336,6 +392,32 @@ sr_report_from_json(struct sr_json_value *root, char **error_message)
+ 
+     }
+ 
++    /* Authentication entries. */
++    struct sr_json_value *extra = json_element(root, "auth");
++    if (extra)
++    {
++        if (!JSON_CHECK_TYPE(extra, SR_JSON_OBJECT, "auth"))
++            goto fail;
++
++        const unsigned children = json_object_children_count(extra);
++
++        /* from the last children down to the first for easier testing :)
++         * keep it as it is as long as sr_report_add_auth() does LIFO */
++        for (unsigned i = 1; i <= children; ++i)
++        {
++            const char *child_name = NULL;
++            struct sr_json_value *child_object = json_object_get_child(extra,
++                                                                       children - i,
++                                                                       &child_name);
++
++            if (!JSON_CHECK_TYPE(child_object, SR_JSON_STRING, child_name))
++                continue;
++
++            const char *child_value = json_string_get_value(child_object);
++            sr_report_add_auth(report, child_name, child_value);
++        }
++    }
++
+     return report;
+ 
+ fail:
+diff --git a/python/py_report.c b/python/py_report.c
+index d314027..8aa35dd 100644
+--- a/python/py_report.c
++++ b/python/py_report.c
+@@ -42,6 +42,8 @@
+ #define to_json_doc "Usage: report.to_json()\n\n" \
+                     "Returns: string - the report serialized as JSON"
+ 
++#define auth_doc "Dictinary of key/value pairs used for authentication"
++
+ /* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
+ #define GSOFF_PY_STRUCT sr_py_report
+ #define GSOFF_PY_MEMBER report
+@@ -64,6 +66,7 @@ report_getset[] =
+     SR_ATTRIBUTE_STRING(component_name,   "Name of the software component this report pertains to (string)"       ),
+     { (char*)"report_version", sr_py_report_get_version, sr_py_setter_readonly, (char*)"Version of the report (int)", NULL },
+     { (char*)"report_type", sr_py_report_get_type, sr_py_report_set_type, (char*)"Report type (string)", NULL },
++    { (char*)"auth",        sr_py_report_get_auth, sr_py_report_set_auth, (char*)auth_doc, NULL               },
+     { NULL },
+ };
+ 
+@@ -492,3 +495,30 @@ sr_py_report_to_json(PyObject *self, PyObject *args)
+     free(json);
+     return result;
+ }
++
++PyObject *
++sr_py_report_get_auth(PyObject *self, void *data)
++{
++    struct sr_report *report = ((struct sr_py_report *)self)->report;
++    struct sr_report_custom_entry *ae = report->auth_entries;
++
++    PyObject *auth = PyDict_New();
++    for (ae = report->auth_entries; ae; ae = ae->next)
++    {
++        PyObject *val = PyString_FromString(ae->value);
++        if (!val)
++            return NULL;
++
++        if (PyDict_SetItemString(auth, ae->key, val) == -1)
++            return NULL;
++    }
++
++    return auth;
++}
++
++int
++sr_py_report_set_auth(PyObject *self, PyObject *rhs, void *data)
++{
++    PyErr_SetString(PyExc_NotImplementedError, "Setting auth data is not implemented.");
++    return -1;
++}
+diff --git a/python/py_report.h b/python/py_report.h
+index a451dbc..88ec30f 100644
+--- a/python/py_report.h
++++ b/python/py_report.h
+@@ -65,6 +65,8 @@ PyObject *sr_py_report_str(PyObject *self);
+ PyObject *sr_py_report_get_version(PyObject *self, void *data);
+ PyObject *sr_py_report_get_type(PyObject *self, void *data);
+ int sr_py_report_set_type(PyObject *self, PyObject *rhs, void *data);
++PyObject *sr_py_report_get_auth(PyObject *self, void *data);
++int sr_py_report_set_auth(PyObject *self, PyObject *rhs, void *data);
+ 
+ /**
+  * Methods.
+diff --git a/tests/json_files/ureport-1-auth b/tests/json_files/ureport-1-auth
+new file mode 100644
+index 0000000..a8db2df
+--- /dev/null
++++ b/tests/json_files/ureport-1-auth
+@@ -0,0 +1,112 @@
++{
++  "ureport_version": 2,
++
++  "reason": "Program /usr/bin/sleep was terminated by signal 11",
++
++  "os": {
++    "name": "fedora",
++    "version": "18",
++    "architecture": "x86_64"
++  },
++
++  "problem": {
++    "type": "core",
++
++    "executable": "/usr/bin/sleep",
++
++    "signal": 11,
++
++    "component": "coreutils",
++
++    "user": {
++      "local": true,
++      "root": false
++    },
++
++    "stacktrace": [
++      {
++        "crash_thread": true,
++
++        "frames": [
++          {
++            "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
++            "build_id_offset": 767024,
++            "file_name": "/lib64/libc.so.6",
++            "address": 251315074096,
++            "fingerprint": "6c1eb9626919a2a5f6a4fc4c2edc9b21b33b7354",
++            "function_name": "__nanosleep"
++          },
++          {
++            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
++            "build_id_offset": 16567,
++            "file_name": "/usr/bin/sleep",
++            "address": 4210871,
++            "fingerprint": "d24433b82a2c751fc580f47154823e0bed641a54",
++            "function_name": "close_stdout"
++          },
++          {
++            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
++            "build_id_offset": 16202,
++            "file_name": "/usr/bin/sleep",
++            "address": 4210506,
++            "fingerprint": "562719fb960d1c4dbf30c04b3cff37c82acc3d2d",
++            "function_name": "close_stdout"
++          },
++          {
++            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
++            "build_id_offset": 6404,
++            "fingerprint": "2e8fb95adafe21d035b9bcb9993810fecf4be657",
++            "file_name": "/usr/bin/sleep",
++            "address": 4200708
++          },
++          {
++            "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
++            "build_id_offset": 137733,
++            "file_name": "/lib64/libc.so.6",
++            "address": 251314444805,
++            "fingerprint": "075acda5d3230e115cf7c88597eaba416bdaa6bb",
++            "function_name": "__libc_start_main"
++          }
++        ]
++      }
++    ]
++  },
++
++  "packages": [
++    {
++      "name": "coreutils",
++      "epoch": 0,
++      "version": "8.17",
++      "architecture": "x86_64",
++      "package_role": "affected",
++      "release": "8.fc18",
++      "install_time": 1371464601
++    },
++    {
++      "name": "glibc",
++      "epoch": 0,
++      "version": "2.16",
++      "architecture": "x86_64",
++      "release": "31.fc18",
++      "install_time": 1371464176
++    },
++    {
++      "name": "glibc-common",
++      "epoch": 0,
++      "version": "2.16",
++      "architecture": "x86_64",
++      "release": "31.fc18",
++      "install_time": 1371464184
++    }
++  ],
++
++  "reporter": {
++    "version": "0.3",
++    "name": "satyr"
++  },
++
++  "auth": {
++    "hostname": "localhost",
++    "machine_id": "0000"
++  }
++}
+diff --git a/tests/python/report.py b/tests/python/report.py
+index 83fa76b..5f731b5 100755
+--- a/tests/python/report.py
++++ b/tests/python/report.py
+@@ -91,6 +91,12 @@ class TestReport(BindingsTestCase):
+     def test_hash(self):
+         self.assertHashable(self.report)
+ 
++    def test_auth(self):
++        self.assertFalse(self.report.auth)
++        self.assertRaises(NotImplementedError, self.report.__setattr__, 'auth', {'hostname': 'darkstar'})
++
++        report_with_auth = satyr.Report(load_input_contents('../json_files/ureport-1-auth'))
++        self.assertEqual(report_with_auth.auth, {'hostname': 'localhost', 'machine_id': '0000'})
+ 
+ if __name__ == '__main__':
+     unittest.main()
+diff --git a/tests/report.at b/tests/report.at
+index 8736137..1e773d2 100644
+--- a/tests/report.at
++++ b/tests/report.at
+@@ -57,3 +57,84 @@ int main(void)
+   return 0;
+ }
+ ]])
++
++## ------------------ ##
++## sr_report_add_auth ##
++## ------------------ ##
++
++AT_TESTFUN([sr_report_add_auth],
++[[
++#include <assert.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include "report.h"
++
++void check_struct(struct sr_report *report, const char **expected)
++{
++    const char **exp_iter = expected;
++    struct sr_report_custom_entry *cust_iter = report->auth_entries;
++
++    while(cust_iter && *exp_iter)
++    {
++        fprintf(stdout, "Expected('%s':'%s') vs. Current('%s':'%s')\n",
++            exp_iter[0], exp_iter[1], cust_iter->key, cust_iter->value);
++
++        assert(strcmp(cust_iter->key, exp_iter[0]) == 0 &&
++               strcmp(cust_iter->value, exp_iter[1]) == 0);
++
++        cust_iter = cust_iter->next;
++        exp_iter += 2;
++    }
++
++    assert(cust_iter == NULL);
++    assert(*exp_iter == NULL);
++}
++
++void check_json(const char *json, const char **expected)
++{
++    const char **exp_iter = expected;
++    while (*exp_iter)
++    {
++        char *entry = NULL;
++        asprintf(&entry, "\"%s\": \"%s\"", exp_iter[0], exp_iter[1]);
++
++        fprintf(stdout, "Checking: '%s'\n", entry);
++
++        if (strstr(json, entry) == NULL)
++        {
++            fprintf(stderr, "JSON:\n%s\n", json);
++            abort();
++        }
++
++        exp_iter += 2;
++    }
++}
++
++
++int main(void)
++{
++  struct sr_report *report = sr_report_new();
++
++  sr_report_add_auth(report, "foo", "blah");
++  sr_report_add_auth(report, "abrt", "awesome");
++  sr_report_add_auth(report, "satyr", "wonderful");
++
++  const char *expected[] = { "satyr", "wonderful", "abrt", "awesome", "foo", "blah", NULL };
++
++  check_struct(report, expected);
++
++  sr_report_to_json(report);
++
++  char *json = sr_report_to_json(report);
++
++  check_json(json, expected);
++
++  char *error = NULL;
++  struct sr_report *copy = sr_report_from_json_text(json, &error);
++
++  check_struct(copy, expected);
++
++  return 0;
++}
++]])
+-- 
+1.9.3
+
diff --git a/SPECS/satyr.spec b/SPECS/satyr.spec
new file mode 100644
index 0000000..ee2824a
--- /dev/null
+++ b/SPECS/satyr.spec
@@ -0,0 +1,331 @@
+%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
+
+# rhel6's python-sphinx cannot build manual pages
+%if 0%{?rhel} && 0%{?rhel} <= 6
+  %define enable_python_manpage 0
+%else
+  %define enable_python_manpage 1
+%endif
+
+%if 0%{?suse_version}
+  %define python2_devel python-devel
+  %define libdw_devel libdw-devel
+  %define libelf_devel libelf-devel
+%else
+  %define python2_devel python2-devel
+  %define libdw_devel elfutils-devel
+  %define libelf_devel elfutils-libelf-devel
+%endif
+
+Name: satyr
+Version: 0.13
+Release: 15%{?dist}
+Summary: Tools to create anonymous, machine-friendly problem reports
+Group: System Environment/Libraries
+License: GPLv2+
+URL: https://github.com/abrt/satyr
+Source0: https://fedorahosted.org/released/abrt/satyr-%{version}.tar.xz
+BuildRequires: %{python2_devel}
+BuildRequires: %{libdw_devel}
+BuildRequires: %{libelf_devel}
+BuildRequires: binutils-devel
+BuildRequires: rpm-devel
+BuildRequires: libtool
+BuildRequires: pkgconfig
+BuildRequires: automake
+BuildRequires: gcc-c++
+%if %{?enable_python_manpage}
+BuildRequires: python-sphinx
+%endif
+
+# git is need for '%%autosetup -S git' which automatically applies all the
+# patches above. Please, be aware that the patches must be generated
+# by 'git format-patch'
+BuildRequires: git
+
+Patch0: satyr-0.13-elfutils-0.158.patch
+Patch1: satyr-0.13-elfutils-unwinder.patch
+Patch2: satyr-0.13-disable-fingerprints.patch
+Patch3: satyr-0.13-unwinder-refresh-config-h.patch
+
+# 1142856, minor bugs found by static analyzer
+Patch4: satyr-0.13-static-analyzer-bugs.patch
+
+# 1123262, empty duphash of unreliable koops
+Patch5: satyr-0.13-koops-unreliable-frames.patch
+
+# 1142339, python exception parsing
+Patch6: satyr-0.13-python-exceptions.patch
+
+# 1142338, ppc64 backtrace parsing
+Patch7: satyr-0.13-ppc64-backtrace-parsing.patch
+
+# 1142346, limit stacktrace depth
+Patch8: satyr-0.13-limit-stacktrace-depth.patch
+
+# 1139555, ureport auth support
+Patch9: satyr-0.13-ureport-auth-support.patch
+
+# 1034857, ignore java suppressed exceptions
+Patch10: satyr-0.13-java-suppressed-exceptions.patch
+
+# 1147952, don't free gdb stacktrace on method failure
+Patch11: satyr-0.13-dont-free-gdb-stacktrace.patch
+
+# 1142346, better handling of infinite recursion
+Patch12: satyr-0.13-better-inf-recursion-handling.patch
+
+# 1210599, add functionality to generate a backtrace without saving a coredump
+Patch13: satyr-0.13-fulfill-missing-values-in-core-frames.patch
+Patch14: satyr-0.13-unwind-minor-refactoring.patch
+Patch15: satyr-0.13-support-unwinding-from-core-hook.patch
+Patch16: satyr-0.13-debug-unwinding-from-core-hook-using-satyr-binary.patch
+Patch17: satyr-0.13-disable-hook-unwind-on-kernels-w-o-PTRACE_SEIZE.patch
+Patch18: satyr-0.13-abrt-refactorize-unwinding-from-core-hook.patch
+Patch19: satyr-0.13-core_unwind-fix-the-missing-frame-build_id-and-file.patch
+
+# 1334604, add support for Ruby
+Patch20: satyr-0.13-Add-support-for-Ruby-report-type.patch
+Patch21: satyr-0.13-python-add-Ruby-support.patch
+
+# 1332869, actualize list of normalization function in satyr
+Patch22: satyr-0.13-normalize-extend-xorg-blacklist.patch
+Patch23: satyr-0.13-normalization-additional-X-GDK-functions.patch
+Patch24: satyr-0.13-normalization-add-glibc-__assert_fail_base.patch
+Patch25: satyr-0.13-normalization-add-glibc-__libc_fatal.patch
+Patch26: satyr-0.13-normalization-normalize-out-exit-frames.patch
+Patch27: satyr-0.13-normalization-actualize-list-of-functions.patch
+
+# 1334604, add support for Ruby testsuite fix
+Patch28: satyr-0.13-tests-fix-failure-on-gcc5-on-x86_64.patch
+
+# 1336390, fix defects found by coverity
+Patch29: satyr-0.13-Fix-defects-found-by-coverity.patch
+Patch30: satyr-0.13-Check-the-return-value-of-sr_parse_char_cspan.patch
+
+# 1342469, support for VARIANT and VARIANT_ID
+Patch31: satyr-0.13-os-add-support-for-OS-Variant.patch
+
+# 1260074, Incorrectly unwinding core_backtrace for stack overflow (aarch64)
+Patch32: satyr-0.13-Honor-frame-number-limit-in-GDB-core-unwinder.patch
+Patch33: satyr-0.13-testsuite-add-test-for-limit-frame-number-in-GDB-cor.patch
+
+%description
+Satyr is a library that can be used to create and process microreports.
+Microreports consist of structured data suitable to be analyzed in a fully
+automated manner, though they do not necessarily contain sufficient information
+to fix the underlying problem. The reports are designed not to contain any
+potentially sensitive data to eliminate the need for review before submission.
+Included is a tool that can create microreports and perform some basic
+operations on them.
+
+%package devel
+Summary: Development libraries for %{name}
+Group: Development/Libraries
+Requires: %{name}%{?_isa} = %{version}-%{release}
+
+%description devel
+Development libraries and headers for %{name}.
+
+%package python
+Summary: Python bindings for %{name}
+Group: Development/Libraries
+Requires: %{name}%{?_isa} = %{version}-%{release}
+
+%description python
+Python bindings for %{name}.
+
+%prep
+# http://www.rpm.org/wiki/PackagerDocs/Autosetup
+# Default '__scm_apply_git' is 'git apply && git commit' but this workflow
+# doesn't allow us to create a new file within a patch, so we have to use
+# 'git am' (see /usr/lib/rpm/macros for more details)
+%define __scm_apply_git(qp:m:) %{__git} am
+%autosetup -S git
+
+%build
+autoreconf
+
+%configure \
+%if ! %{?enable_python_manpage}
+        --disable-python-manpage \
+%endif
+        --disable-static
+
+make %{?_smp_mflags}
+
+%install
+make install DESTDIR=%{buildroot}
+
+# Remove all libtool archives (*.la) from modules directory.
+find %{buildroot} -name "*.la" | xargs rm --
+
+%check
+make check || {
+    # find and print the logs of failed test
+    # do not cat tests/testsuite.log because it contains a lot of bloat
+    find tests -name "testsuite.log" -print -exec cat '{}' \;
+    exit 1
+}
+
+
+%post -p /sbin/ldconfig
+%postun -p /sbin/ldconfig
+
+%files
+%doc README NEWS COPYING
+%{_bindir}/satyr
+%{_mandir}/man1/%{name}.1*
+%{_libdir}/lib*.so.*
+
+%files devel
+%{_includedir}/*
+%{_libdir}/lib*.so
+%{_libdir}/pkgconfig/*
+
+%files python
+%dir %{python_sitearch}/%{name}
+%{python_sitearch}/%{name}/*
+
+%if %{?enable_python_manpage}
+%{_mandir}/man3/satyr-python.3*
+%endif
+
+%changelog
+* Tue Jun 19 2018 Matej Marusak <mmarusak@redhat.com> - 0.13-15
+- Honor frame number limit in GDB core unwinder
+  - Related: #1260074
+
+* Mon Jun 06 2016 Matej Habrnal <mhabrnal@redhat.com> - 0.13-14
+- add support for OS Variant
+  - Related: #1342469
+
+* Thu May 12 2016 Matej Habrnal <mhabrnal@redhat.com> - 0.13-13
+- add support for Ruby
+  - Related: #1334604
+- actualize list of normalization function in satyr
+  - Related: #1332869
+- fix defects found by coverity
+  - Related: #1336390
+
+* Wed Sep 9 2015 Richard Marko <rmarko@redhat.com> - 0.13-12
+- apply last patch
+  - Related: #1210599
+
+* Wed Sep 9 2015 Richard Marko <rmarko@redhat.com> - 0.13-11
+- core unwind: fix the missing frame build_id and file_name
+  - Related: #1210599
+
+* Fri Jul 17 2015 Richard Marko <rmarko@redhat.com> - 0.13-10
+- leave saving of core backtrace to abrt hook
+  - Related: #1210599
+
+* Tue Jun 23 2015 Richard Marko <rmarko@redhat.com> - 0.13-9
+- Add functionality to generate a backtrace without saving a coredump
+  - Resolves: #1210599
+
+* Wed Nov 19 2014 Martin Milata <mmilata@redhat.com> - 0.13-8
+- Better handling of stacktraces with infinite recursion
+  - Resolves: #1142346
+
+* Fri Oct 03 2014 Martin Milata <mmilata@redhat.com> - 0.13-7
+- Don't free GDB stacktrace on error
+  - Resolves: #1147952
+
+* Fri Oct 03 2014 Martin Milata <mmilata@redhat.com> - 0.13-6
+- Ignore suppressed exceptions in the Java exception parser
+  - Resolves: #1034857
+
+* Thu Sep 18 2014 Martin Milata <mmilata@redhat.com> - 0.13-5
+- Fix minor bugs found by static analyzers
+  - Resolves: #1142856
+- Return empty duphash for koopses with no reliable frames
+  - Resolves: #1123262
+- Fix parsing of python SyntaxError exceptions
+  - Resolves: #1142339
+- Fix parsing of ppc64 gdb stacktraces
+  - Resolves: #1142338
+- Limit the depth of generated stacktrace to avoid huge reports
+  - Resolves: #1142346
+- Add authentication support to uReport, needed for reporting to customer portal
+  - Resolves: #1139555
+
+* Fri Jan 24 2014 Daniel Mach <dmach@redhat.com> - 0.13-4
+- Mass rebuild 2014-01-24
+
+* Wed Jan 22 2014 Martin Milata <mmilata@redhat.com> 0.13-3
+- Fix build with elfutils unwinder
+  - Resolves: #1051569
+
+* Tue Jan 14 2014 Martin Milata <mmilata@redhat.com> 0.13-2
+- Use elfutils unwinder
+  - Resolves: #1051569
+- Disable function fingerprinting
+  - Resolves: #1052402
+
+* Tue Jan 07 2014 Martin Milata <mmilata@redhat.com> 0.13-1
+- Rebase to satyr-0.13
+  - Resolves: #1040900
+- Includes patch to build against elfutils-0.158
+
+* Fri Dec 27 2013 Daniel Mach <dmach@redhat.com> - 0.9-3
+- Mass rebuild 2013-12-27
+
+* Wed Dec 04 2013 Martin Milata <mmilata@redhat.com> 0.9-2
+- Fix malformed JSON for some Java and koops reports
+  - Resolves: #1035317
+  - Resolves: #1036790
+- Fix memory leak in RPM handling
+  - Resolves: #1016780
+- Check for unsigned overflows
+  - Resolves: #1034869
+
+* Wed Sep 11 2013 Jakub Filak <jfilak@redhat.com> 0.9-1
+- New upstream version
+  - Enrich koops uReport data with koops text and kernel version.
+  - Improve koops modules handling.
+
+* Wed Aug 28 2013 Richard Marko<rmarko@redhat.com> 0.8-1
+- New upstream version
+  - Added support for json de/serialization of reports and stacktraces.
+  - Library version number increased, as the interface changed since the last release
+
+* Mon Aug 26 2013 Martin Milata <mmilata@redhat.com> 0.7-1
+- New upstream version
+  - Fix couple of crashes (#997076, #994747)
+
+* Mon Jul 29 2013 Martin Milata <mmilata@redhat.com> 0.6-1
+- New upstream version
+  - Do not export internal function symbols.
+
+* Thu Jul 25 2013 Martin Milata <mmilata@redhat.com> 0.5-2
+- Remove libunwind dependency altogether, always use GDB for unwinding.
+
+* Thu Jul 25 2013 Jakub Filak <jfilak@redhat.com> 0.5-1
+- Added function that creates core stacktrace from GDB output. Several bugfixes.
+
+* Tue Jul 09 2013 Martin Milata <mmilata@redhat.com> 0.4-2
+- Fix failing tests (failure manifests only on s390x)
+
+* Mon Jul 08 2013 Martin Milata <mmilata@redhat.com> 0.4-1
+- New upstream version
+  - Added features needed by ABRT
+  - Support for uReport2
+  - Major C and Python API changes
+- Patch for python-2.6 compatibility
+
+* Tue Apr 02 2013 Dan HorĂ¡k <dan[at]danny.cz> 0.3-2
+- libunwind exists only on selected arches
+
+* Mon Mar 25 2013 Martin Milata <mmilata@redhat.com> 0.3-1
+- New upstream version
+  - Bug fixes
+  - Build fixes for older systems
+- Do not require libunwind on rhel
+
+* Mon Mar 18 2013 Martin Milata <mmilata@redhat.com> 0.2-1
+- Documentation and spec cleanup
+- Build fixes (build against RPM)
+
+* Mon Aug 30 2010 Karel Klic <kklic@redhat.com> 0.1-1
+- Upstream package spec file