Mac端循环创建webrtcbin触发heap-use-after-free段错误
问题现象
同一份基于GStreamer webrtcbin 实现多源媒体流接收的代码,Windows平台可正常运行,Mac平台下for循环创建webrtcbin数量超过1个时就会触发段错误,报错类型为 heap-use-after-free(堆内存释放后重用)。
问题根因
代码存在多处内存管理不规范问题,Windows平台C运行库内存回收策略宽松,悬空指针访问时对应内存内容未被篡改,所以暂时表现为运行正常;Mac平台libc内存回收机制更激进,释放的内存会被快速标记为不可访问或重新分配给其他逻辑,因此会稳定触发内存访问错误,具体问题点如下:
- 无效堆内存分配:循环中先后给
peerid、webrtc指针调用g_malloc0分配指针大小的堆内存,紧接着就用API返回值覆盖了这两个指针,之前分配的内存直接泄漏,没有任何实际作用。 - 悬空指针风险:
json_array_get_string_element返回的字符串由JsonArray对象内部持有,所有权没有转移给调用方,JsonArray生命周期变动或循环迭代过程中该字符串内存会被自动释放,后续异步回调中再访问这个地址就会触发堆内存重用错误。 - 回调参数内存生命周期无管理:给信号回调传入的
g_strdup(peerid)分配的字符串没有绑定释放逻辑,既会造成内存泄漏,也可能因为内存被提前回收出现非法访问。 - GStreamer引用计数不匹配:
gst_bin_get_by_name返回的webrtc元素会自动新增一个引用计数,代码中使用完该指针后没有调用gst_object_unref匹配释放,长期运行会造成引用泄漏,打乱进程内存布局,放大内存问题触发概率。 - 临时资源未释放:
gst_caps_from_string创建的caps、Json对象、动态生成的SDP字符串等临时资源用完后没有对应释放逻辑,会造成额外内存泄漏。
修复方案
针对上述问题逐一修正,严格匹配GLib/GStreamer的内存管理和引用计数规则:
- 删除循环中对
peerid、webrtc无意义的g_malloc0调用,两个变量仅作为指针存储API返回值即可,不需要额外在堆上分配指针大小的内存。 - 从JsonArray拿到peerid字符串后,立刻调用
g_strdup拷贝一份独立持有所有权,作为信号回调的传入参数;使用g_signal_connect_data替代g_signal_connect,传入g_free作为闭包销毁回调,信号断开时自动释放拷贝的字符串内存,既避免泄漏也保证回调执行期间内存有效。 - 所有通过GStreamer/GLib API获取的带引用计数的对象、动态分配的临时资源,用完后严格匹配对应的释放接口,保证引用计数平衡。
修正后的核心代码如下:
static void on_offer_created(GstPromise *promise, gpointer data) { GstWebRTCSessionDescription *offer = NULL; const GstStructure *reply; gchar *sdp_string; gchar *json_string; reply = gst_promise_get_reply(promise); GstElement *webrtc; webrtc = gst_bin_get_by_name(GST_BIN(gst_pipe), (gchar *)data); g_assert_nonnull(webrtc); gst_structure_get(reply, "offer", GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL); gst_promise_unref(promise); g_signal_emit_by_name(webrtc, "set-local-description", offer, NULL); sdp_string = gst_sdp_message_as_text(offer->sdp); g_print(" offer created:\n%s\n", sdp_string); JsonObject *sdp_json = json_object_new(); json_object_set_string_member(sdp_json, "type", "offer"); json_object_set_string_member(sdp_json, "sdp", sdp_string); json_object_set_string_member(sdp_json, "from", ourid); json_object_set_string_member(sdp_json, "to", (gchar *)data); json_string = get_string_from_json_object(sdp_json); soup_websocket_connection_send_text(connection, json_string); g_print("sending offer to %s",(gchar *)data); // 匹配释放所有临时资源 gst_webrtc_session_description_free(offer); g_free(sdp_string); g_free(json_string); json_object_unref(sdp_json); // 释放gst_bin_get_by_name新增的引用,保证引用计数平衡 gst_object_unref(webrtc); } static void on_negotiation_needed(GstElement *webrtc, gpointer user_data) { GstPromise *promise; g_print("negotiation needed\n"); promise = gst_promise_new_with_change_func(on_offer_created, user_data, NULL); g_signal_emit_by_name(webrtc, "create-offer", NULL, promise); } // 修正后的webrtcbin创建循环 for (int i = json_array_get_length(chain)-1; i >= 0; i--) { const gchar *tmp_peerid = json_array_get_string_element(chain, i); // 独立拷贝peerid,绑定到信号回调生命周期 gchar *peerid = g_strdup(tmp_peerid); // 直接接收元素工厂返回的指针,删除无意义的malloc GstElement *webrtc = gst_element_factory_make("webrtcbin", peerid); g_assert(webrtc != NULL); gst_bin_add_many(GST_BIN(gst_pipe), webrtc, NULL); GstCaps *caps = gst_caps_from_string(TRANS_AUDIO_CAPS); g_signal_emit_by_name(webrtc, "add-transceiver", GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY, caps, NULL); // 用完caps立刻释放 gst_caps_unref(caps); g_signal_connect(webrtc, "pad-added", G_CALLBACK(on_incoming_stream), &peer_delay); // 带销毁回调注册信号,信号销毁时自动g_free释放peerid内存 g_signal_connect_data(webrtc, "on-negotiation-needed", G_CALLBACK(on_negotiation_needed), peerid, (GClosureNotify)g_free, 0); g_signal_connect_data(webrtc, "on-ice-candidate", G_CALLBACK(send_ice_candidate_message), g_strdup(peerid), (GClosureNotify)g_free, 0); g_assert_true(gst_element_sync_state_with_parent(webrtc)); }
内容的提问来源于stack exchange,提问作者Usama
相关产品推荐
相关产品推荐

